Skip to content

Commit

Permalink
Merge pull request #25 from zheng-ze/main
Browse files Browse the repository at this point in the history
Add ContactSystem, HomeSystem and Cost Checking for spawns
  • Loading branch information
Vanessamae23 authored Mar 23, 2024
2 parents 74ce857 + 6b5c66a commit d5425d4
Show file tree
Hide file tree
Showing 19 changed files with 290 additions and 85 deletions.
12 changes: 12 additions & 0 deletions TowerForge/TowerForge.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
3CE9515F2BADE2C5008B2785 /* ShootingSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE9515E2BADE2C5008B2785 /* ShootingSystem.swift */; };
3CE951612BADE881008B2785 /* Spawnable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE951602BADE881008B2785 /* Spawnable.swift */; };
3CE951632BAE037C008B2785 /* AiSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE951622BAE037C008B2785 /* AiSystem.swift */; };
3CE951652BAE0A04008B2785 /* HomeSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE951642BAE0A04008B2785 /* HomeSystem.swift */; };
3CE951672BAEAB0E008B2785 /* ContactSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE951662BAEAB0E008B2785 /* ContactSystem.swift */; };
3CE951692BAEB719008B2785 /* RequestSpawnEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE951682BAEB719008B2785 /* RequestSpawnEvent.swift */; };
5200624E2BA8D597000DBA30 /* AiComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5200624D2BA8D597000DBA30 /* AiComponent.swift */; };
520062522BA8DA09000DBA30 /* UnitGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520062512BA8DA09000DBA30 /* UnitGenerator.swift */; };
520062562BA8E026000DBA30 /* PlayerSpawnable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520062552BA8E026000DBA30 /* PlayerSpawnable.swift */; };
Expand Down Expand Up @@ -130,6 +133,9 @@
3CE9515E2BADE2C5008B2785 /* ShootingSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShootingSystem.swift; sourceTree = "<group>"; };
3CE951602BADE881008B2785 /* Spawnable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Spawnable.swift; sourceTree = "<group>"; };
3CE951622BAE037C008B2785 /* AiSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AiSystem.swift; sourceTree = "<group>"; };
3CE951642BAE0A04008B2785 /* HomeSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeSystem.swift; sourceTree = "<group>"; };
3CE951662BAEAB0E008B2785 /* ContactSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactSystem.swift; sourceTree = "<group>"; };
3CE951682BAEB719008B2785 /* RequestSpawnEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestSpawnEvent.swift; sourceTree = "<group>"; };
5200624D2BA8D597000DBA30 /* AiComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AiComponent.swift; sourceTree = "<group>"; };
520062512BA8DA09000DBA30 /* UnitGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitGenerator.swift; sourceTree = "<group>"; };
520062552BA8E026000DBA30 /* PlayerSpawnable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerSpawnable.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -240,6 +246,8 @@
BA443D3C2BAD9557009F0FFB /* RemoveSystem.swift */,
3CE9515E2BADE2C5008B2785 /* ShootingSystem.swift */,
3CE951622BAE037C008B2785 /* AiSystem.swift */,
3CE951662BAEAB0E008B2785 /* ContactSystem.swift */,
3CE951642BAE0A04008B2785 /* HomeSystem.swift */,
);
path = Systems;
sourceTree = "<group>";
Expand All @@ -252,6 +260,7 @@
3C9955CB2BA5889800D33FA5 /* MoveEvent.swift */,
3C9955C72BA5865C00D33FA5 /* ConcurrentEvent.swift */,
BA443D3E2BAD9774009F0FFB /* RemoveEvent.swift */,
3CE951682BAEB719008B2785 /* RequestSpawnEvent.swift */,
);
path = "Implemented Events";
sourceTree = "<group>";
Expand Down Expand Up @@ -693,10 +702,13 @@
527E3A242BA613F000FE1628 /* PlayerComponent.swift in Sources */,
3C9955C52BA585DD00D33FA5 /* HealthSystem.swift in Sources */,
520062562BA8E026000DBA30 /* PlayerSpawnable.swift in Sources */,
3CE951652BAE0A04008B2785 /* HomeSystem.swift in Sources */,
52DF5FFB2BA3601400135367 /* HealthComponent.swift in Sources */,
3C9955BC2BA563A800D33FA5 /* TFEvent.swift in Sources */,
BA443D3F2BAD9774009F0FFB /* RemoveEvent.swift in Sources */,
5295A2072BAA02FD005018A8 /* TFButton.swift in Sources */,
3CE951672BAEAB0E008B2785 /* ContactSystem.swift in Sources */,
3CE951692BAEB719008B2785 /* RequestSpawnEvent.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
52 changes: 12 additions & 40 deletions TowerForge/TowerForge/GameWorld.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@ class GameWorld {
private var selectionNode: UnitSelectionNode
private var grid: Grid
private var renderer: Renderer?
private var entitiesInContact: Set<TFContact> = []

init(scene: GameScene?, screenSize: CGRect) {
self.scene = scene
entityManager = EntityManager()
systemManager = SystemManager()
eventManager = EventManager()
selectionNode = UnitSelectionNode()
grid = Grid(entityManager: entityManager, screenSize: screenSize)
grid = Grid(eventManager: eventManager, screenSize: screenSize)
if let scene = self.scene {
grid.generateTileMap(scene: scene)
}
Expand All @@ -35,10 +34,6 @@ class GameWorld {
}

func update(deltaTime: TimeInterval) {
for contact in entitiesInContact {
handleContact(between: contact.entityIdA, and: contact.entityIdB)
}

systemManager.update(deltaTime)
eventManager.executeEvents(in: self)
renderer?.render()
Expand All @@ -47,57 +42,34 @@ class GameWorld {
func spawnUnit(at location: CGPoint) {
selectionNode.unitNodeDidSpawn(location)
}

func setupTeam() {
let ownTeam = Team(player: .ownPlayer, entityManager: entityManager)
let oppositeTeam = Team(player: .oppositePlayer, entityManager: entityManager)
let ownTeam = Team(player: .ownPlayer)
let oppositeTeam = Team(player: .oppositePlayer)
entityManager.add(ownTeam)
entityManager.add(oppositeTeam)
}

// TODO: Move contact handling to a system
func contactDidBegin(between idA: UUID, and idB: UUID) {
guard idA != idB, entityManager.entity(with: idA) != nil, entityManager.entity(with: idB) != nil else {
return
}

entitiesInContact.insert(TFContact(entityIdA: idA, entityIdB: idB))
systemManager.system(ofType: ContactSystem.self)?.insert(contact: TFContact(entityIdA: idA, entityIdB: idB))
}

func contactDidEnd(between idA: UUID, and idB: UUID) {
guard entitiesInContact.remove(TFContact(entityIdA: idA, entityIdB: idB)) != nil else {
return
}

handleSeparation(between: idA, and: idB)
}

private func handleContact(between idA: UUID, and idB: UUID) {
guard let entityA = entityManager.entity(with: idA), let entityB = entityManager.entity(with: idB) else {
entitiesInContact.remove(TFContact(entityIdA: idA, entityIdB: idB))
return
}

guard let event = entityA.collide(with: entityB) else {
return
}

eventManager.add(event)
}

private func handleSeparation(between idA: UUID, and idB: UUID) {
guard let entityA = entityManager.entity(with: idA), let entityB = entityManager.entity(with: idB) else {
return
}
// TODO: Handle any separation logic here.
systemManager.system(ofType: ContactSystem.self)?.remove(contact: TFContact(entityIdA: idA, entityIdB: idB))
}

private func setUpSystems() {
systemManager.add(system: HealthSystem(entityManager: entityManager, eventManager: eventManager))
systemManager.add(system: MovementSystem(entityManager: entityManager, eventManager: eventManager))
systemManager.add(system: MovementSystem(entityManager: entityManager))
systemManager.add(system: RemoveSystem(entityManager: entityManager, eventManager: eventManager))
systemManager.add(system: SpawnSystem(entityManager: entityManager, eventManager: eventManager))
systemManager.add(system: ShootingSystem(entityManager: entityManager, eventManager: eventManager))
systemManager.add(system: HomeSystem(entityManager: entityManager, eventManager: eventManager))
systemManager.add(system: AiSystem(entityManager: entityManager, eventManager: eventManager))
systemManager.add(system: ContactSystem(entityManager: entityManager, eventManager: eventManager))

// Temporary until we have different gamemodes
systemManager.system(ofType: AiSystem.self)?.aiPlayers.append(.oppositePlayer)
}

private func setUpSelectionNode() {
Expand Down
10 changes: 5 additions & 5 deletions TowerForge/TowerForge/Grid.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,22 @@ import SpriteKit
class Grid: UnitSelectionNodeDelegate {
let DEFAULT_NO_OF_ROWS = 8

private var entityManager: EntityManager
private var eventManager: EventManager
private var noOfRows: Int
private var width: CGFloat
private var height: CGFloat

init(entityManager: EntityManager, screenSize: CGRect) {
self.entityManager = entityManager
init(eventManager: EventManager, screenSize: CGRect) {
self.eventManager = eventManager
self.noOfRows = DEFAULT_NO_OF_ROWS
self.width = screenSize.width
self.height = screenSize.height
}

func unitSelectionNodeDidSpawn<T: TFEntity & PlayerSpawnable>(ofType type: T.Type, position: CGPoint) {
let snapPosition = CGPoint(x: position.x, y: snapYPosition(yPosition: position.y))
let unit = UnitGenerator.spawn(ofType: type, at: snapPosition, player: Player.ownPlayer)
entityManager.add(unit)
eventManager.add(RequestSpawnEvent(ofType: type, timestamp: CACurrentMediaTime(),
position: snapPosition, player: .ownPlayer))
}

func generateTileMap(scene: SKScene) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ protocol Collidable {
func collide(with other: Collidable) -> TFEvent?
func collide(with damageComponent: DamageComponent) -> TFEvent?
func collide(with healthComponent: HealthComponent) -> TFEvent?
func collide(with movableComponent: MovableComponent) -> TFEvent?
}
45 changes: 26 additions & 19 deletions TowerForge/TowerForge/LevelManager/Components/AiComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,38 @@ import Foundation
import UIKit

class AiComponent: TFComponent {
private var entityManager: EntityManager
private var chosenUnit: (TFEntity & PlayerSpawnable).Type?
init(entityManager: EntityManager) {
self.entityManager = entityManager
self.chosenUnit = SpawnableEntities.playerSpawnableEntities.randomElement()
private var spawnableEntities: [(TFEntity & PlayerSpawnable).Type]
private(set) var delayUnitNextSpawn: TimeInterval

var chosenUnit: (TFEntity & PlayerSpawnable).Type? {
spawnableEntities.randomElement()
}

var spawnLocation: CGPoint {
let randomY = CGFloat.random(in: 0...UIScreen.main.bounds.height)
return CGPoint(x: UIScreen.main.bounds.width, y: randomY)
}

init(spawnableEntities: [(TFEntity & PlayerSpawnable).Type] = SpawnableEntities.playerSpawnableEntities) {
self.spawnableEntities = spawnableEntities
self.delayUnitNextSpawn = AiComponent.getNextInterval()
super.init()
}

override func update(deltaTime: TimeInterval) {
guard let homeComponent = entity?.component(ofType: HomeComponent.self),
let chosenUnit = chosenUnit else {
return
delayUnitNextSpawn -= deltaTime
}

func spawn() -> Bool {
guard delayUnitNextSpawn <= 0 else {
return false
}
let randomY = CGFloat.random(in: 0...UIScreen.main.bounds.height)

if homeComponent.points >= chosenUnit.cost {
let unit = UnitGenerator.spawn(ofType: chosenUnit,
at: CGPoint(x: UIScreen.main.bounds.width,
y: randomY),
player: .oppositePlayer)
homeComponent.decreasePoints(chosenUnit.cost)
delayUnitNextSpawn = AiComponent.getNextInterval()
return true
}

// Re-randomize the unit
self.chosenUnit = SpawnableEntities.playerSpawnableEntities.randomElement()
entityManager.add(unit)
}
private static func getNextInterval() -> TimeInterval {
Double.random(in: 1...10)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import CoreGraphics
class MovableComponent: TFComponent {
var velocity: CGVector
var position: CGPoint
var isColliding = false

init(position: CGPoint, velocity: CGVector = .zero) {
self.velocity = velocity
Expand All @@ -28,7 +29,8 @@ class MovableComponent: TFComponent {
}

override func update(deltaTime: TimeInterval) {
guard let entity = entity,
guard !isColliding,
let entity = entity,
let positionComponent = entity.component(ofType: PositionComponent.self),
let playerComponent = entity.component(ofType: PlayerComponent.self) else {
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
import Foundation

class BaseProjectile: TFEntity {
init(textureNames: [String], size: CGSize, key: String, position: CGPoint, player: Player, velocity: CGVector = .zero) {
init(textureNames: [String], size: CGSize, key: String, position: CGPoint,
player: Player, velocity: CGVector = .zero) {
super.init()

createSpriteComponent(textureNames: textureNames, size: size, key: key, position: position)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ class BaseTower: TFEntity {
return damageComponent.damage(healthComponent)
}

override func collide(with movableComponent: MovableComponent) -> (any TFEvent)? {
guard let playerA = self.component(ofType: PlayerComponent.self)?.player,
let playerB = movableComponent.entity?.component(ofType: PlayerComponent.self)?.player,
playerA != playerB else {
return nil
}

movableComponent.isColliding = true
return nil
}

private func createHealthComponent(maxHealth: CGFloat) {
let healthComponent = HealthComponent(maxHealth: maxHealth)
self.addComponent(healthComponent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ class BaseUnit: TFEntity {

override func collide(with other: any Collidable) -> TFEvent? {
let superEvent = super.collide(with: other)
guard let healthComponent = self.component(ofType: HealthComponent.self) else {
guard let healthComponent = self.component(ofType: HealthComponent.self),
let movableComponent = self.component(ofType: MovableComponent.self) else {
return superEvent
}

if let superEvent = superEvent {
return superEvent.concurrentlyWith(other.collide(with: healthComponent))
}
return other.collide(with: healthComponent)
let event = other.collide(with: healthComponent)?
.concurrentlyWith(other.collide(with: movableComponent)) ?? other.collide(with: movableComponent)

return superEvent?.concurrentlyWith(event) ?? event
}

override func collide(with damageComponent: DamageComponent) -> TFEvent? {
Expand All @@ -43,6 +44,21 @@ class BaseUnit: TFEntity {
return damageComponent.damage(healthComponent)
}

override func collide(with movableComponent: MovableComponent) -> TFEvent? {
guard let playerA = self.component(ofType: PlayerComponent.self)?.player,
let playerB = movableComponent.entity?.component(ofType: PlayerComponent.self)?.player,
playerA != playerB else {
return nil
}

movableComponent.isColliding = true
if let component = self.component(ofType: MovableComponent.self) {
component.isColliding = true
}

return nil
}

private func createHealthComponent(maxHealth: CGFloat) {
let healthComponent = HealthComponent(maxHealth: maxHealth)
self.addComponent(healthComponent)
Expand Down
4 changes: 2 additions & 2 deletions TowerForge/TowerForge/LevelManager/Entities/Team.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ class Team: TFEntity {
static let lifeCount = 5
static let pointsInterval = TimeInterval(0.5)
var player: Player
init(player: Player, entityManager: EntityManager) {
init(player: Player) {
self.player = player
super.init()
createPlayerComponent(player: player)
createHomeComponent()

if player == .oppositePlayer {
self.addComponent(AiComponent(entityManager: entityManager))
self.addComponent(AiComponent())
}
}
private func createHomeComponent() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ struct MoveEvent: TFEvent {
return nil
}
movementSystem.handleMovement(for: entityId, with: displacement)
return EventOutput()
return nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ struct RemoveEvent: TFEvent {
}

removeSystem.handleRemove(for: entityId)
return EventOutput()
return nil
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// RequestSpawnEvent.swift
// TowerForge
//
// Created by Zheng Ze on 23/3/24.
//

import Foundation

struct RequestSpawnEvent: TFEvent {
let timestamp: TimeInterval
let position: CGPoint
let entityType: (PlayerSpawnable & TFEntity).Type
let player: Player

var entityId: UUID {
fatalError("entityId is not to be used here")
}

init<T: TFEntity & PlayerSpawnable>(ofType type: T.Type, timestamp: TimeInterval,
position: CGPoint, player: Player) {
self.timestamp = timestamp
self.position = position
self.entityType = type
self.player = player
}

func execute(in target: any EventTarget) -> EventOutput? {
guard let homeSystem = target.system(ofType: HomeSystem.self) else {
return nil
}
homeSystem.attemptSpawn(at: position, ofType: entityType, for: player)
return nil
}
}
Loading

0 comments on commit d5425d4

Please sign in to comment.