Skip to content

Commit

Permalink
Merge pull request #58 from zheng-ze/main
Browse files Browse the repository at this point in the history
Add spawns location logic
  • Loading branch information
zheng-ze authored Mar 30, 2024
2 parents 247beaf + 8c8be62 commit 52434dd
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 96 deletions.
166 changes: 108 additions & 58 deletions TowerForge/TowerForge/AppMain/Storyboards/Base.lproj/Main.storyboard

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class ArrowTower: BaseTower, PlayerSpawnable {
static let maxHealth = 200.0
static let damage = 10.0
static var cost = 10
static let range = 400.0
static let fireRate = 1.0

required init(position: CGPoint, player: Player) {
Expand All @@ -25,7 +26,7 @@ class ArrowTower: BaseTower, PlayerSpawnable {
maxHealth: ArrowTower.maxHealth,
player: player)
self.addComponent(ShootingComponent(fireRate: ArrowTower.fireRate,
range: 1.0,
range: ArrowTower.range,
attackPower: ArrowTower.damage))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Created by Zheng Ze on 23/3/24.
//

import QuartzCore
import UIKit

class HomeSystem: TFSystem {
var isActive = true
Expand Down Expand Up @@ -34,6 +34,7 @@ class HomeSystem: TFSystem {
_ = playerHomeComponent.decreaseLife(by: life)
}
}

func changeDeathCount(for player: Player, change: Int) {
let playerHomeComponentArr = entityManager.components(ofType: HomeComponent.self).filter(({
$0.entity?.component(ofType: PlayerComponent.self)?.player == player
Expand All @@ -42,6 +43,7 @@ class HomeSystem: TFSystem {
playerHomeComponent.changeDeathCount(change)
}
}

func attemptSpawn<T: TFEntity & PlayerSpawnable>(at position: CGPoint, ofType type: T.Type, for player: Player) {
// Get HomeComponent for the player
let playerHomeComponentArr = entityManager.components(ofType: HomeComponent.self).filter(({
Expand All @@ -52,16 +54,48 @@ class HomeSystem: TFSystem {
}

// Check if they have enough points to spawn
for playerHomeComponent in playerHomeComponentArr {
guard playerHomeComponent.points >= type.cost else {
return
}
playerHomeComponent.decreasePoints(type.cost)
guard !playerHomeComponentArr.contains(where: { $0.points < type.cost }) else {
return
}

let snapPosition = CGPoint(x: position.x, y: gridDelegate.snapYPosition(yPosition: position.y))
// Reduce points
playerHomeComponentArr.forEach({ $0.decreasePoints(type.cost) })

let snapPosition = getSnapPosition(at: position, for: player, with: type)
let spawnEvent = SpawnEvent(ofType: type, timestamp: CACurrentMediaTime(),
position: snapPosition, player: player)
eventManager.add(spawnEvent)
}

private func getSnapPosition<T: TFEntity & PlayerSpawnable>(at requestedPosition: CGPoint,
for player: Player, with type: T.Type) -> CGPoint {
// Normalise to own player position
let normalisedPosition = normalise(position: requestedPosition, for: player)

// Snap x position according to unit type
let snappedPosition = snap(position: normalisedPosition, for: type)

// Undo normalise if needed
let denormalisedPosition = normalise(position: snappedPosition, for: player)

return gridDelegate.snap(position: denormalisedPosition)
}

private func normalise(position: CGPoint, for player: Player) -> CGPoint {
guard player == .oppositePlayer else {
return position
}

return CGPoint(x: gridDelegate.playableBounds.maxX - position.x, y: position.y)
}

private func snap<T: TFEntity & PlayerSpawnable>(position: CGPoint, for type: T.Type) -> CGPoint {
if type is BaseUnit.Type {
return CGPoint(x: 0, y: position.y)
} else if type is BaseTower.Type {
let maxX = gridDelegate.playableBounds.midX - gridDelegate.tileSize.width / 2
return position.x > maxX ? CGPoint(x: maxX, y: position.y) : position
}
return position
}
}
57 changes: 30 additions & 27 deletions TowerForge/TowerForge/LevelModule/Grid/Grid.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,47 @@
import SpriteKit

class Grid: GridDelegate {
let DEFAULT_NO_OF_ROWS = 5
static let DEFAULT_NUM_ROWS = 5
let UNIT_SELECTION_NODE_HEIGHT = CGFloat(200)

private var noOfRows: Int
private var width: CGFloat
private var height: CGFloat

init(screenSize: CGRect) {
self.noOfRows = DEFAULT_NO_OF_ROWS
self.width = screenSize.width
self.height = screenSize.height
let playableBounds: CGRect

private let numRows: Int
private var numCols: Int { Int(ceil(screenWidth / tileSize.width)) }
private var screenWidth: CGFloat { playableBounds.width }
private var screenHeight: CGFloat { playableBounds.height - UNIT_SELECTION_NODE_HEIGHT }
var tileSize: CGSize { CGSize(width: screenHeight / CGFloat(numRows),
height: screenHeight / CGFloat(numRows)) }

init(screenSize: CGRect, numRows: Int = Grid.DEFAULT_NUM_ROWS) {
self.playableBounds = screenSize
self.numRows = numRows
}

func generateTileMap(scene: SKScene) {
let screenWidth = self.width
let screenHeight = self.height - UNIT_SELECTION_NODE_HEIGHT
let tileSize = CGSize(width: screenHeight / CGFloat(noOfRows), height: screenHeight / CGFloat(noOfRows))

// Calculate the number of columns needed to cover the screen width
let numberOfColumns = Int(ceil(screenWidth / tileSize.width))

for row in 0..<noOfRows {
for col in 0..<numberOfColumns {
let tileSize = self.tileSize
for row in 0..<numRows {
for col in 0..<numCols {
let node = TFSpriteNode(imageName: "road-tile", size: tileSize)
let position = CGPoint(x: CGFloat(col) * tileSize.width,
y: CGFloat(row) * tileSize.height + UNIT_SELECTION_NODE_HEIGHT)
node.anchorPoint = CGPoint(x: 0, y: 0)
node.position = CGPoint(x: CGFloat(CGFloat(col) * tileSize.width),
y: CGFloat(CGFloat(row) * tileSize.height) + UNIT_SELECTION_NODE_HEIGHT)
node.position = normaliseToPlayableBounds(position: position)
node.zPosition = -100
scene.addChild(node.node)
}
}
}

func snapYPosition(yPosition: Double) -> Double {
let screenHeight = Double(UIScreen.main.bounds.height) - UNIT_SELECTION_NODE_HEIGHT
let rowHeight = screenHeight / Double(noOfRows)
let rowIndex = Int((yPosition - UNIT_SELECTION_NODE_HEIGHT) / rowHeight)
let centerY = (Double(rowIndex) * rowHeight + rowHeight / 2) + UNIT_SELECTION_NODE_HEIGHT
return centerY
func snap(position: CGPoint) -> CGPoint {
let rowIndex = Int((position.y - UNIT_SELECTION_NODE_HEIGHT) / tileSize.height)
let colIndex = Int(position.x / tileSize.width)

let centerY = (Double(rowIndex) + 0.5) * tileSize.height + UNIT_SELECTION_NODE_HEIGHT
let centerX = (Double(colIndex) + 0.5) * tileSize.width
return CGPoint(x: centerX, y: centerY)
}

private func normaliseToPlayableBounds(position: CGPoint) -> CGPoint {
CGPoint(x: position.x + playableBounds.minX, y: position.y + playableBounds.minY)
}
}
4 changes: 3 additions & 1 deletion TowerForge/TowerForge/LevelModule/Grid/GridDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ import QuartzCore

protocol GridDelegate: AnyObject {
var UNIT_SELECTION_NODE_HEIGHT: CGFloat { get }
func snapYPosition(yPosition: Double) -> Double
var playableBounds: CGRect { get }
var tileSize: CGSize { get }
func snap(position: CGPoint) -> CGPoint
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ class HealthRenderStage: RenderStage {

let healthNode = TFSpriteNode(color: HealthRenderStage.color, size: HealthRenderStage.size)
healthNode.name = HealthRenderStage.name
healthNode.position = CGPoint(x: -spriteComponent.size.width / 2,
y: spriteComponent.size.height / 2 + HealthRenderStage.size.height + 10)
healthNode.position = CGPoint(x: -spriteComponent.size.width / 2, y: spriteComponent.size.height / 2 + 5)
healthNode.anchorPoint = CGPoint(x: 0, y: 0)
node.add(child: healthNode)
}
Expand Down

0 comments on commit 52434dd

Please sign in to comment.