From 3160d3981ac7a42e148f1a5d206e540c459608d2 Mon Sep 17 00:00:00 2001 From: zheng-ze Date: Tue, 26 Mar 2024 21:53:59 +0800 Subject: [PATCH] Update ContactSystem to not rely on view and move RemoveEvent generation to HealthSystem --- .../TowerForge.xcodeproj/project.pbxproj | 4 + .../BaseComponents/MovableComponent.swift | 23 ++--- .../BaseComponents/PositionComponent.swift | 7 +- .../GameComponents/ContactComponent.swift | 20 +++++ .../GameComponents/DamageComponent.swift | 4 +- .../GameComponents/ShootingComponent.swift | 27 +----- .../BaseEntities/BaseProjectile.swift | 3 +- .../Entities/BaseEntities/BaseTower.swift | 5 +- .../Entities/BaseEntities/BaseUnit.swift | 19 +++-- .../Entities/GameEntities/Life.swift | 7 +- .../Entities/GameEntities/Point.swift | 6 +- .../GameModule/Entities/TFEntity.swift | 2 + .../GameModule/Events/ConcurrentEvent.swift | 13 +-- .../GameModule/Events/EventOutput.swift | 4 +- .../Events/GameEvents/DamageEvent.swift | 9 +- .../TowerForge/GameModule/GameEngine.swift | 13 +-- .../TowerForge/GameModule/GameWorld.swift | 15 +--- .../Systems/BaseSystems/ContactSystem.swift | 85 ++++++++++++------- .../Systems/GameSystems/ShootingSystem.swift | 33 ++++++- .../TowerForge/GameViewController.swift | 16 ---- .../LevelModule/Collision/Collidable.swift | 1 + .../TowerForge/Nodes/UnitSelectionNode.swift | 3 +- TowerForge/TowerForge/Scenes/GameScene.swift | 24 ------ .../Scenes/Rendering/Renderable.swift | 2 +- .../Scenes/Rendering/Renderer.swift | 2 +- .../SceneDelegates/SceneUpdateDelegate.swift | 2 - .../TowerForge/TFCore/TFSpriteNode.swift | 9 -- 27 files changed, 165 insertions(+), 193 deletions(-) create mode 100644 TowerForge/TowerForge/GameModule/Components/GameComponents/ContactComponent.swift diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 379d976b..ca7d22b6 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 3C0B608D2BB2B84000FFECB4 /* ContactComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C0B608C2BB2B84000FFECB4 /* ContactComponent.swift */; }; 3C769A722BA58DE700F454F9 /* MovementSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C769A712BA58DE700F454F9 /* MovementSystem.swift */; }; 3C769A742BA591BD00F454F9 /* SpawnSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C769A732BA591BD00F454F9 /* SpawnSystem.swift */; }; 3C9955A12BA47DA500D33FA5 /* BaseTower.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C9955A02BA47DA500D33FA5 /* BaseTower.swift */; }; @@ -116,6 +117,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 3C0B608C2BB2B84000FFECB4 /* ContactComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactComponent.swift; sourceTree = ""; }; 3C769A712BA58DE700F454F9 /* MovementSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovementSystem.swift; sourceTree = ""; }; 3C769A732BA591BD00F454F9 /* SpawnSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpawnSystem.swift; sourceTree = ""; }; 3C9955A02BA47DA500D33FA5 /* BaseTower.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTower.swift; sourceTree = ""; }; @@ -307,6 +309,7 @@ isa = PBXGroup; children = ( 52578B862BA6209700B4D76C /* DamageComponent.swift */, + 3C0B608C2BB2B84000FFECB4 /* ContactComponent.swift */, 52DF5FFE2BA3656500135367 /* ShootingComponent.swift */, 52DF5FFA2BA3601400135367 /* HealthComponent.swift */, ); @@ -919,6 +922,7 @@ 527E3A242BA613F000FE1628 /* PlayerComponent.swift in Sources */, 3C9955C52BA585DD00D33FA5 /* HealthSystem.swift in Sources */, 520062562BA8E026000DBA30 /* PlayerSpawnable.swift in Sources */, + 3C0B608D2BB2B84000FFECB4 /* ContactComponent.swift in Sources */, 3CE951652BAE0A04008B2785 /* HomeSystem.swift in Sources */, 52DF5FFB2BA3601400135367 /* HealthComponent.swift in Sources */, 3C9955BC2BA563A800D33FA5 /* TFEvent.swift in Sources */, diff --git a/TowerForge/TowerForge/GameModule/Components/BaseComponents/MovableComponent.swift b/TowerForge/TowerForge/GameModule/Components/BaseComponents/MovableComponent.swift index 5b9fa2bf..d8ee7d1a 100644 --- a/TowerForge/TowerForge/GameModule/Components/BaseComponents/MovableComponent.swift +++ b/TowerForge/TowerForge/GameModule/Components/BaseComponents/MovableComponent.swift @@ -10,27 +10,22 @@ import CoreGraphics class MovableComponent: TFComponent { var velocity: CGVector - var position: CGPoint - var isColliding = false + var shouldMove = true - init(position: CGPoint, velocity: CGVector = .zero) { + init(velocity: CGVector = .zero) { self.velocity = velocity - self.position = position super.init() } - func updatePosition(to position: CGPoint) { - self.position = position - } - - func updatePosition(with vector: CGVector) { - self.position.x += vector.dx - self.position.y += vector.dy - } + func updatePosition(with vector: CGVector) { + guard let positionComponent = entity?.component(ofType: PositionComponent.self) else { + return + } + positionComponent.move(by: vector) + } override func update(deltaTime: TimeInterval) { - guard !isColliding, - let entity = entity, + guard shouldMove, let entity = entity, let positionComponent = entity.component(ofType: PositionComponent.self), let playerComponent = entity.component(ofType: PlayerComponent.self) else { return diff --git a/TowerForge/TowerForge/GameModule/Components/BaseComponents/PositionComponent.swift b/TowerForge/TowerForge/GameModule/Components/BaseComponents/PositionComponent.swift index 646b06ec..fefcf247 100644 --- a/TowerForge/TowerForge/GameModule/Components/BaseComponents/PositionComponent.swift +++ b/TowerForge/TowerForge/GameModule/Components/BaseComponents/PositionComponent.swift @@ -9,7 +9,7 @@ import Foundation import CoreGraphics class PositionComponent: TFComponent { - var position: CGPoint + private(set) var position: CGPoint var anchorPoint: CGPoint init(position: CGPoint, anchorPoint: CGPoint) { @@ -25,4 +25,9 @@ class PositionComponent: TFComponent { func changeTo(to position: CGPoint) { self.position = position } + + func move(by displacement: CGVector) { + position.x += displacement.dx + position.y += displacement.dy + } } diff --git a/TowerForge/TowerForge/GameModule/Components/GameComponents/ContactComponent.swift b/TowerForge/TowerForge/GameModule/Components/GameComponents/ContactComponent.swift new file mode 100644 index 00000000..dcc73cbd --- /dev/null +++ b/TowerForge/TowerForge/GameModule/Components/GameComponents/ContactComponent.swift @@ -0,0 +1,20 @@ +// +// ContactComponent.swift +// TowerForge +// +// Created by Zheng Ze on 26/3/24. +// + +import Foundation + +class ContactComponent: TFComponent { + let hitboxSize: CGSize + + init(hitboxSize: CGSize) { + self.hitboxSize = hitboxSize + } + + func hitbox(position: CGPoint) -> CGRect { + CGRect(origin: position, size: hitboxSize) + } +} diff --git a/TowerForge/TowerForge/GameModule/Components/GameComponents/DamageComponent.swift b/TowerForge/TowerForge/GameModule/Components/GameComponents/DamageComponent.swift index 7c1e7a06..cbed9546 100644 --- a/TowerForge/TowerForge/GameModule/Components/GameComponents/DamageComponent.swift +++ b/TowerForge/TowerForge/GameModule/Components/GameComponents/DamageComponent.swift @@ -25,7 +25,7 @@ class DamageComponent: TFComponent { } func damage(_ healthComponent: HealthComponent) -> TFEvent? { - guard canDamage, let enemyId = healthComponent.entity?.id, let id = entity?.id else { + guard canDamage, let enemyId = healthComponent.entity?.id, let entityId = entity?.id else { return nil } @@ -39,7 +39,7 @@ class DamageComponent: TFComponent { let event = DamageEvent(on: enemyId, at: lastAttackTime, with: attackPower) if temporary { - return event.concurrentlyWith(RemoveEvent(on: id, at: lastAttackTime)) + return event.concurrentlyWith(RemoveEvent(on: entityId, at: lastAttackTime)) } return event } diff --git a/TowerForge/TowerForge/GameModule/Components/GameComponents/ShootingComponent.swift b/TowerForge/TowerForge/GameModule/Components/GameComponents/ShootingComponent.swift index 0dd54b69..0f5723b7 100644 --- a/TowerForge/TowerForge/GameModule/Components/GameComponents/ShootingComponent.swift +++ b/TowerForge/TowerForge/GameModule/Components/GameComponents/ShootingComponent.swift @@ -10,7 +10,7 @@ import QuartzCore class ShootingComponent: TFComponent { var fireRate: TimeInterval // Delay between shots var range: CGFloat - private var lastShotTime = TimeInterval(0) + private(set) var lastShotTime = TimeInterval(0) let attackPower: CGFloat init(fireRate: TimeInterval, range: CGFloat, attackPower: CGFloat) { @@ -24,30 +24,7 @@ class ShootingComponent: TFComponent { CACurrentMediaTime() - lastShotTime >= fireRate } - func shoot(_ healthComponent: HealthComponent) -> SpawnEvent? { - guard canShoot, let entityA = self.entity, let entityB = healthComponent.entity else { - return nil - } - - guard let playerA = entityA.component(ofType: PlayerComponent.self)?.player, - let playerB = entityB.component(ofType: PlayerComponent.self)?.player, - playerA != playerB else { - return nil - } - - guard let positionA = entityA.component(ofType: PositionComponent.self)?.position, - let positionB = entityB.component(ofType: PositionComponent.self)?.position else { - return nil - } - - let direction = playerA == .ownPlayer ? -1.0 : 1.0 - let distance = (positionA.x - positionB.x) * direction - - guard distance > 0, distance <= range, abs(positionA.y - positionB.y) <= 50 else { - return nil - } - + func shoot() { lastShotTime = CACurrentMediaTime() - return SpawnEvent(ofType: Bullet.self, timestamp: lastShotTime, position: positionA, player: playerA) } } diff --git a/TowerForge/TowerForge/GameModule/Entities/BaseEntities/BaseProjectile.swift b/TowerForge/TowerForge/GameModule/Entities/BaseEntities/BaseProjectile.swift index 0c3a0954..da946a6b 100644 --- a/TowerForge/TowerForge/GameModule/Entities/BaseEntities/BaseProjectile.swift +++ b/TowerForge/TowerForge/GameModule/Entities/BaseEntities/BaseProjectile.swift @@ -14,9 +14,10 @@ class BaseProjectile: TFEntity { // Core Components self.addComponent(SpriteComponent(textureNames: textureNames, size: size, animatableKey: key)) self.addComponent(PositionComponent(position: position)) - self.addComponent(MovableComponent(position: position, velocity: velocity)) + self.addComponent(MovableComponent(velocity: velocity)) // Game Components self.addComponent(PlayerComponent(player: player)) + self.addComponent(ContactComponent(hitboxSize: size)) } } diff --git a/TowerForge/TowerForge/GameModule/Entities/BaseEntities/BaseTower.swift b/TowerForge/TowerForge/GameModule/Entities/BaseEntities/BaseTower.swift index 2c46fd60..140a9ac7 100644 --- a/TowerForge/TowerForge/GameModule/Entities/BaseEntities/BaseTower.swift +++ b/TowerForge/TowerForge/GameModule/Entities/BaseEntities/BaseTower.swift @@ -18,10 +18,11 @@ class BaseTower: TFEntity { // Core Components self.addComponent(SpriteComponent(textureNames: textureNames, size: size, animatableKey: key)) self.addComponent(PositionComponent(position: position)) - + // Game Components self.addComponent(HealthComponent(maxHealth: maxHealth)) self.addComponent(PlayerComponent(player: player)) + self.addComponent(ContactComponent(hitboxSize: size)) } override func collide(with other: any Collidable) -> TFEvent? { @@ -51,7 +52,7 @@ class BaseTower: TFEntity { return nil } - movableComponent.isColliding = true + movableComponent.shouldMove = true return nil } } diff --git a/TowerForge/TowerForge/GameModule/Entities/BaseEntities/BaseUnit.swift b/TowerForge/TowerForge/GameModule/Entities/BaseEntities/BaseUnit.swift index a58c84ec..24ce1d01 100644 --- a/TowerForge/TowerForge/GameModule/Entities/BaseEntities/BaseUnit.swift +++ b/TowerForge/TowerForge/GameModule/Entities/BaseEntities/BaseUnit.swift @@ -19,11 +19,12 @@ class BaseUnit: TFEntity { // Core Components self.addComponent(SpriteComponent(textureNames: textureNames, size: size, animatableKey: key)) self.addComponent(PositionComponent(position: position)) - self.addComponent(MovableComponent(position: position, velocity: velocity)) - + self.addComponent(MovableComponent(velocity: velocity)) + // Game Components self.addComponent(HealthComponent(maxHealth: maxHealth)) self.addComponent(PlayerComponent(player: player)) + self.addComponent(ContactComponent(hitboxSize: size)) } override func collide(with other: any Collidable) -> TFEvent? { @@ -39,6 +40,13 @@ class BaseUnit: TFEntity { return superEvent?.concurrentlyWith(event) ?? event } + override func onSeparate() { + guard let movableComponent = self.component(ofType: MovableComponent.self) else { + return + } + movableComponent.shouldMove = true + } + override func collide(with damageComponent: DamageComponent) -> TFEvent? { guard let healthComponent = self.component(ofType: HealthComponent.self) else { return nil @@ -54,11 +62,10 @@ class BaseUnit: TFEntity { return nil } - movableComponent.isColliding = true - if let component = self.component(ofType: MovableComponent.self) { - component.isColliding = true + movableComponent.shouldMove = false + if let ownMovableComponent = self.component(ofType: MovableComponent.self) { + ownMovableComponent.shouldMove = false } - return nil } } diff --git a/TowerForge/TowerForge/GameModule/Entities/GameEntities/Life.swift b/TowerForge/TowerForge/GameModule/Entities/GameEntities/Life.swift index 8513a219..e02e11dc 100644 --- a/TowerForge/TowerForge/GameModule/Entities/GameEntities/Life.swift +++ b/TowerForge/TowerForge/GameModule/Entities/GameEntities/Life.swift @@ -9,13 +9,10 @@ import Foundation class Life: TFEntity { static let position = CGPoint(x: 300, y: 100) + static let size = CGSize(width: 100, height: 100) init(initialLife: Int) { super.init() - self.addComponent(SpriteComponent(textureNames: ["Life"], - height: 100, - width: 100, - position: Life.position, - animatableKey: "life")) + self.addComponent(SpriteComponent(textureNames: ["Life"], size: Life.size, animatableKey: "life")) self.addComponent(HomeComponent(initialLifeCount: Team.lifeCount, pointInterval: Team.pointsInterval)) self.addComponent(LabelComponent(text: String(initialLife), name: "life")) self.addComponent(PositionComponent(position: Life.position)) diff --git a/TowerForge/TowerForge/GameModule/Entities/GameEntities/Point.swift b/TowerForge/TowerForge/GameModule/Entities/GameEntities/Point.swift index d31e714c..6e1c2b4b 100644 --- a/TowerForge/TowerForge/GameModule/Entities/GameEntities/Point.swift +++ b/TowerForge/TowerForge/GameModule/Entities/GameEntities/Point.swift @@ -9,12 +9,10 @@ import Foundation class Point: TFEntity { static let position = CGPoint(x: 100, y: 100) + static let size = CGSize(width: 100, height: 100) init(initialPoint: Int) { super.init() - self.addComponent(SpriteComponent(textureNames: ["Coin"], - height: 100, - width: 100, - position: Point.position, animatableKey: "point")) + self.addComponent(SpriteComponent(textureNames: ["Coin"], size: Point.size, animatableKey: "point")) self.addComponent(HomeComponent(initialLifeCount: Team.lifeCount, pointInterval: Team.pointsInterval)) self.addComponent(LabelComponent(text: String(initialPoint), name: "point")) self.addComponent(PositionComponent(position: Point.position)) diff --git a/TowerForge/TowerForge/GameModule/Entities/TFEntity.swift b/TowerForge/TowerForge/GameModule/Entities/TFEntity.swift index 60e2d362..8aec2295 100644 --- a/TowerForge/TowerForge/GameModule/Entities/TFEntity.swift +++ b/TowerForge/TowerForge/GameModule/Entities/TFEntity.swift @@ -54,6 +54,8 @@ class TFEntity: Collidable { nil } + func onSeparate() {} + func collide(with damageComponent: DamageComponent) -> TFEvent? { /// assert(checkRepresentation()) nil diff --git a/TowerForge/TowerForge/GameModule/Events/ConcurrentEvent.swift b/TowerForge/TowerForge/GameModule/Events/ConcurrentEvent.swift index fd582dc5..1e70527c 100644 --- a/TowerForge/TowerForge/GameModule/Events/ConcurrentEvent.swift +++ b/TowerForge/TowerForge/GameModule/Events/ConcurrentEvent.swift @@ -22,15 +22,10 @@ struct ConcurrentEvent: TFEvent { } func execute(in target: any EventTarget) -> EventOutput? { - guard var eventOutput = event1.execute(in: target) else { - return nil - } + var eventOutput1 = event1.execute(in: target) + let eventOutput2 = event2.execute(in: target) - guard let eventOutput2 = event2.execute(in: target) else { - return eventOutput - } - - eventOutput.combine(with: eventOutput2) - return eventOutput + eventOutput1?.combine(with: eventOutput2) + return eventOutput1 != nil ? eventOutput1 : eventOutput2 } } diff --git a/TowerForge/TowerForge/GameModule/Events/EventOutput.swift b/TowerForge/TowerForge/GameModule/Events/EventOutput.swift index c2da90be..f83a38db 100644 --- a/TowerForge/TowerForge/GameModule/Events/EventOutput.swift +++ b/TowerForge/TowerForge/GameModule/Events/EventOutput.swift @@ -21,7 +21,7 @@ struct EventOutput { } extension EventOutput { - mutating func combine(with otherEventOuput: EventOutput) { - self.events.append(contentsOf: otherEventOuput.events) + mutating func combine(with otherEventOuput: EventOutput?) { + self.events.append(contentsOf: otherEventOuput?.events ?? []) } } diff --git a/TowerForge/TowerForge/GameModule/Events/GameEvents/DamageEvent.swift b/TowerForge/TowerForge/GameModule/Events/GameEvents/DamageEvent.swift index 35c31f9e..e055a15b 100644 --- a/TowerForge/TowerForge/GameModule/Events/GameEvents/DamageEvent.swift +++ b/TowerForge/TowerForge/GameModule/Events/GameEvents/DamageEvent.swift @@ -23,12 +23,7 @@ struct DamageEvent: TFEvent { guard let healthSystem = target.system(ofType: HealthSystem.self) else { return nil } - let healthIsZero = healthSystem.modifyHealth(for: entityId, with: -damage) - var eventOutput = EventOutput() - if healthIsZero { - eventOutput.add(RemoveEvent(on: entityId, at: timestamp)) - } - - return eventOutput + healthSystem.modifyHealth(for: entityId, with: -damage) + return nil } } diff --git a/TowerForge/TowerForge/GameModule/GameEngine.swift b/TowerForge/TowerForge/GameModule/GameEngine.swift index 73e1eb56..f9bad3dc 100644 --- a/TowerForge/TowerForge/GameModule/GameEngine.swift +++ b/TowerForge/TowerForge/GameModule/GameEngine.swift @@ -20,9 +20,6 @@ protocol AbstractGameEngine: EventTarget { func updateGame(deltaTime: TimeInterval) func setUpSystems(with grid: Grid) - func contactDidBegin(between idA: UUID, and idB: UUID) - func contactDidEnd(between idA: UUID, and idB: UUID) - func addEntity(_ entity: TFEntity) func addEvent(_ event: TFEvent) @@ -80,7 +77,7 @@ class GameEngine: AbstractGameEngine { func setUpSystems(with grid: Grid) { systemManager.add(system: HealthSystem(entityManager: entityManager, eventManager: eventManager)) - systemManager.add(system: MovementSystem(entityManager: entityManager)) + systemManager.add(system: MovementSystem(entityManager: entityManager, eventManager: eventManager)) systemManager.add(system: RemoveSystem(entityManager: entityManager, eventManager: eventManager)) systemManager.add(system: SpawnSystem(entityManager: entityManager, eventManager: eventManager)) systemManager.add(system: ShootingSystem(entityManager: entityManager, eventManager: eventManager)) @@ -93,14 +90,6 @@ class GameEngine: AbstractGameEngine { systemManager.system(ofType: AiSystem.self)?.aiPlayers.append(.oppositePlayer) } - func contactDidBegin(between idA: UUID, and idB: UUID) { - systemManager.system(ofType: ContactSystem.self)?.insert(contact: TFContact(entityIdA: idA, entityIdB: idB)) - } - - func contactDidEnd(between idA: UUID, and idB: UUID) { - systemManager.system(ofType: ContactSystem.self)?.remove(contact: TFContact(entityIdA: idA, entityIdB: idB)) - } - func addEntity(_ entity: TFEntity) { entityManager.add(entity) } diff --git a/TowerForge/TowerForge/GameModule/GameWorld.swift b/TowerForge/TowerForge/GameModule/GameWorld.swift index a640a1af..a612ffa7 100644 --- a/TowerForge/TowerForge/GameModule/GameWorld.swift +++ b/TowerForge/TowerForge/GameModule/GameWorld.swift @@ -37,20 +37,8 @@ class GameWorld { selectionNode.unitNodeDidSpawn(location) } - func contactDidBegin(between idA: UUID, and idB: UUID) { - gameEngine.contactDidEnd(between: idA, and: idB) - } - - func contactDidEnd(between idA: UUID, and idB: UUID) { - gameEngine.contactDidEnd(between: idA, and: idB) - } - private func setUpSelectionNode() { selectionNode.delegate = self -// scene?.addChild(selectionNode) -// // Position unit selection node on the left side of the screen -// selectionNode.position = CGPoint(x: 500, y: selectionNode.height / 2) -// // Calculate vertical spacing between unit nodes var horizontalX = 10.0 // Position unit nodes vertically aligned @@ -65,8 +53,7 @@ class GameWorld { } extension GameWorld: Renderable { - func entitiesToRender() -> [TFEntity] { - + var entitiesToRender: [TFEntity] { gameEngine.entities } } diff --git a/TowerForge/TowerForge/GameModule/Systems/BaseSystems/ContactSystem.swift b/TowerForge/TowerForge/GameModule/Systems/BaseSystems/ContactSystem.swift index 3eae5299..6a4c3059 100644 --- a/TowerForge/TowerForge/GameModule/Systems/BaseSystems/ContactSystem.swift +++ b/TowerForge/TowerForge/GameModule/Systems/BaseSystems/ContactSystem.swift @@ -9,59 +9,80 @@ import Foundation class ContactSystem: TFSystem { var isActive = true - weak var entityManager: EntityManager? - weak var eventManager: EventManager? - - private var contacts: Set = [] + unowned var entityManager: EntityManager + unowned var eventManager: EventManager + private var contacts: Set init(entityManager: EntityManager, eventManager: EventManager) { self.entityManager = entityManager self.eventManager = eventManager + self.contacts = [] } func update(within time: CGFloat) { + let contactComponents = entityManager.components(ofType: ContactComponent.self) + var contactsToBeSeparated = contacts + contacts = evaluate(contactComponents: contactComponents) + for contact in contacts { - handleContact(between: contact.entityIdA, and: contact.entityIdB) + contactsToBeSeparated.remove(contact) } - } - func insert(contact: TFContact) { - guard contact.entityIdA != contact.entityIdB, entityManager?.entity(with: contact.entityIdA) != nil, - entityManager?.entity(with: contact.entityIdB) != nil else { - return + for contact in contactsToBeSeparated { + if let entityA = entityManager.entity(with: contact.entityIdA) { + handleSeparation(for: entityA) + } + + if let entityB = entityManager.entity(with: contact.entityIdB) { + handleSeparation(for: entityB) + } } - contacts.insert(contact) - } - func remove(contact: TFContact) { - if contacts.remove(contact) != nil { - handleSeparation(between: contact.entityIdA, and: contact.entityIdB) + for contact in contacts { + guard let entityA = entityManager.entity(with: contact.entityIdA), + let entityB = entityManager.entity(with: contact.entityIdB) else { + continue + } + handleContact(between: entityA, and: entityB) } } - private func handleContact(between idA: UUID, and idB: UUID) { - guard let entityA = entityManager?.entity(with: idA), let entityB = entityManager?.entity(with: idB) else { - contacts.remove(TFContact(entityIdA: idA, entityIdB: idB)) - handleSeparation(between: idA, and: idB) - return + private func evaluate(contactComponents: [ContactComponent]) -> Set { + var contacts: Set = [] + for i in 0.. TFEvent? { + guard shootingComponent.canShoot, let shootingEntity = shootingComponent.entity, + let targetEntity = healthComponent.entity else { + return nil + } + + guard let shootingPlayer = shootingEntity.component(ofType: PlayerComponent.self)?.player, + let targetPlayer = targetEntity.component(ofType: PlayerComponent.self)?.player, + shootingPlayer != targetPlayer else { + return nil + } + + guard let shootingPosition = shootingEntity.component(ofType: PositionComponent.self)?.position, + let targetPosition = targetEntity.component(ofType: PositionComponent.self)?.position else { + return nil + } + + let direction = shootingPlayer == .ownPlayer ? -1.0 : 1.0 + let distance = (shootingPosition.x - targetPosition.x) * direction + + guard distance > 0, distance <= shootingComponent.range, + abs(shootingPosition.y - targetPosition.y) <= 50 else { + return nil + } + + shootingComponent.shoot() + return SpawnEvent(ofType: Bullet.self, timestamp: shootingComponent.lastShotTime, + position: shootingPosition, player: shootingPlayer) + } } diff --git a/TowerForge/TowerForge/GameViewController.swift b/TowerForge/TowerForge/GameViewController.swift index 7169d03a..c3d77568 100644 --- a/TowerForge/TowerForge/GameViewController.swift +++ b/TowerForge/TowerForge/GameViewController.swift @@ -45,22 +45,6 @@ extension GameViewController: SceneUpdateDelegate { func update(deltaTime: TimeInterval) { gameWorld?.update(deltaTime: deltaTime) } - - func contactBegin(between nodeA: TFSpriteNode, and nodeB: TFSpriteNode) { - guard let nameA = nodeA.name, let nameB = nodeB.name, - let idA = UUID(uuidString: nameA), let idB = UUID(uuidString: nameB) else { - return - } - gameWorld?.contactDidBegin(between: idA, and: idB) - } - - func contactEnd(between nodeA: TFSpriteNode, and nodeB: TFSpriteNode) { - guard let nameA = nodeA.name, let nameB = nodeB.name, - let idA = UUID(uuidString: nameA), let idB = UUID(uuidString: nameB) else { - return - } - gameWorld?.contactDidEnd(between: idA, and: idB) - } } extension GameViewController: SceneManagerDelegate { diff --git a/TowerForge/TowerForge/LevelModule/Collision/Collidable.swift b/TowerForge/TowerForge/LevelModule/Collision/Collidable.swift index deaf017e..b7ea5756 100644 --- a/TowerForge/TowerForge/LevelModule/Collision/Collidable.swift +++ b/TowerForge/TowerForge/LevelModule/Collision/Collidable.swift @@ -9,6 +9,7 @@ import Foundation protocol Collidable { func collide(with other: Collidable) -> TFEvent? + func onSeparate() func collide(with damageComponent: DamageComponent) -> TFEvent? func collide(with healthComponent: HealthComponent) -> TFEvent? func collide(with movableComponent: MovableComponent) -> TFEvent? diff --git a/TowerForge/TowerForge/Nodes/UnitSelectionNode.swift b/TowerForge/TowerForge/Nodes/UnitSelectionNode.swift index 6e7d286b..09c45e51 100644 --- a/TowerForge/TowerForge/Nodes/UnitSelectionNode.swift +++ b/TowerForge/TowerForge/Nodes/UnitSelectionNode.swift @@ -27,8 +27,7 @@ class UnitSelectionNode: TFEntity, UnitNodeDelegate { // Temporary until render pipeline is up // Initialised with dummy texture so that it doesn't crash - let spriteComponent = SpriteComponent(textureNames: ["life"], height: 0, width: 0, - position: CGPoint(x: 0, y: 0), animatableKey: "selectionNode") + let spriteComponent = SpriteComponent(textureNames: ["life"], size: CGSize.zero, animatableKey: "selectionNode") let node = spriteComponent.node node.isUserInteractionEnabled = true diff --git a/TowerForge/TowerForge/Scenes/GameScene.swift b/TowerForge/TowerForge/Scenes/GameScene.swift index c4757864..29c040a8 100644 --- a/TowerForge/TowerForge/Scenes/GameScene.swift +++ b/TowerForge/TowerForge/Scenes/GameScene.swift @@ -13,11 +13,6 @@ class GameScene: SKScene { unowned var updateDelegate: SceneUpdateDelegate? unowned var sceneManagerDelegate: SceneManagerDelegate? - override func sceneDidLoad() { - super.sceneDidLoad() - physicsWorld.contactDelegate = self - } - override func touchesBegan(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first else { return @@ -36,22 +31,3 @@ class GameScene: SKScene { updateDelegate?.update(deltaTime: changeInTime) } } - -extension GameScene: SKPhysicsContactDelegate { - public func didBegin(_ contact: SKPhysicsContact) { - guard let nodeA = contact.bodyA.node as? TFSpriteNode, - let nodeB = contact.bodyB.node as? TFSpriteNode else { - return - } - updateDelegate?.contactBegin(between: nodeA, and: nodeB) - } - - public func didEnd(_ contact: SKPhysicsContact) { - guard let nodeA = contact.bodyA.node as? TFSpriteNode, - let nodeB = contact.bodyB.node as? TFSpriteNode else { - return - } - - updateDelegate?.contactEnd(between: nodeA, and: nodeB) - } -} diff --git a/TowerForge/TowerForge/Scenes/Rendering/Renderable.swift b/TowerForge/TowerForge/Scenes/Rendering/Renderable.swift index 400a23d8..141c9a78 100644 --- a/TowerForge/TowerForge/Scenes/Rendering/Renderable.swift +++ b/TowerForge/TowerForge/Scenes/Rendering/Renderable.swift @@ -8,5 +8,5 @@ import Foundation protocol Renderable: AnyObject { - func entitiesToRender() -> [TFEntity] + var entitiesToRender: [TFEntity] { get } } diff --git a/TowerForge/TowerForge/Scenes/Rendering/Renderer.swift b/TowerForge/TowerForge/Scenes/Rendering/Renderer.swift index 720102dd..2b5e05e4 100644 --- a/TowerForge/TowerForge/Scenes/Rendering/Renderer.swift +++ b/TowerForge/TowerForge/Scenes/Rendering/Renderer.swift @@ -21,7 +21,7 @@ class Renderer { func render() { var nodesToBeRemoved = renderedNodes - for entity in target.entitiesToRender() { + for entity in target.entitiesToRender { guard nodesToBeRemoved[entity.id] != nil else { addAndCache(entity: entity) continue diff --git a/TowerForge/TowerForge/Scenes/SceneDelegates/SceneUpdateDelegate.swift b/TowerForge/TowerForge/Scenes/SceneDelegates/SceneUpdateDelegate.swift index 17e973e0..bb8e9bae 100644 --- a/TowerForge/TowerForge/Scenes/SceneDelegates/SceneUpdateDelegate.swift +++ b/TowerForge/TowerForge/Scenes/SceneDelegates/SceneUpdateDelegate.swift @@ -10,6 +10,4 @@ import Foundation protocol SceneUpdateDelegate: AnyObject { func update(deltaTime: TimeInterval) func touch(at location: CGPoint) - func contactBegin(between nodeA: TFSpriteNode, and nodeB: TFSpriteNode) - func contactEnd(between nodeA: TFSpriteNode, and nodeB: TFSpriteNode) } diff --git a/TowerForge/TowerForge/TFCore/TFSpriteNode.swift b/TowerForge/TowerForge/TFCore/TFSpriteNode.swift index 1f283281..ca400c2b 100644 --- a/TowerForge/TowerForge/TFCore/TFSpriteNode.swift +++ b/TowerForge/TowerForge/TFCore/TFSpriteNode.swift @@ -21,7 +21,6 @@ class TFSpriteNode: SKSpriteNode { self.width = width self.height = height super.init(texture: textures?.mainTexture, color: .clear, size: CGSize(width: width, height: height)) - setUpPhysicsBody() } init(imageName: String, height: CGFloat, width: CGFloat) { @@ -34,12 +33,4 @@ class TFSpriteNode: SKSpriteNode { required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - // Only for collision detection - private func setUpPhysicsBody() { - self.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: width, height: height)) - self.physicsBody?.affectedByGravity = false - self.physicsBody?.collisionBitMask = .min - self.physicsBody?.contactTestBitMask = .max - } }