Skip to content

Commit

Permalink
Merge pull request #21 from zheng-ze/main
Browse files Browse the repository at this point in the history
Fix bug where contact is only handled once. Update damage event generation to respect the component's attack rate.
  • Loading branch information
Vanessamae23 authored Mar 22, 2024
2 parents 76b250e + 62a93f9 commit cacc6ce
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 62 deletions.
4 changes: 4 additions & 0 deletions TowerForge/TowerForge.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
3CE9514F2BAC8936008B2785 /* Renderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE9514E2BAC8936008B2785 /* Renderer.swift */; };
3CE951512BAC8955008B2785 /* Renderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE951502BAC8955008B2785 /* Renderable.swift */; };
3CE951562BACA0CF008B2785 /* Collidable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE951552BACA0CF008B2785 /* Collidable.swift */; };
3CE951582BAD724D008B2785 /* TFContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE951572BAD724D008B2785 /* TFContact.swift */; };
5200624E2BA8D597000DBA30 /* AiComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5200624D2BA8D597000DBA30 /* AiComponent.swift */; };
520062522BA8DA09000DBA30 /* UnitGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520062512BA8DA09000DBA30 /* UnitGenerator.swift */; };
520062562BA8E026000DBA30 /* Spawnable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520062552BA8E026000DBA30 /* Spawnable.swift */; };
Expand Down Expand Up @@ -118,6 +119,7 @@
3CE9514E2BAC8936008B2785 /* Renderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Renderer.swift; sourceTree = "<group>"; };
3CE951502BAC8955008B2785 /* Renderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Renderable.swift; sourceTree = "<group>"; };
3CE951552BACA0CF008B2785 /* Collidable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Collidable.swift; sourceTree = "<group>"; };
3CE951572BAD724D008B2785 /* TFContact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TFContact.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 /* Spawnable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Spawnable.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -258,6 +260,7 @@
isa = PBXGroup;
children = (
3CE951552BACA0CF008B2785 /* Collidable.swift */,
3CE951572BAD724D008B2785 /* TFContact.swift */,
);
path = Collision;
sourceTree = "<group>";
Expand Down Expand Up @@ -610,6 +613,7 @@
3C9955A32BA47DBB00D33FA5 /* BaseUnit.swift in Sources */,
3C9955BA2BA5637200D33FA5 /* DamageEvent.swift in Sources */,
3C9955CA2BA5888F00D33FA5 /* SpawnEvent.swift in Sources */,
3CE951582BAD724D008B2785 /* TFContact.swift in Sources */,
5295A2132BAAEA16005018A8 /* UnitNode.swift in Sources */,
52DF5FF32BA351E100135367 /* SpriteComponent.swift in Sources */,
3CE9514B2BAC83FA008B2785 /* SpawnableEntities.swift in Sources */,
Expand Down
9 changes: 5 additions & 4 deletions TowerForge/TowerForge/GameViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,20 @@ extension GameViewController: SceneUpdateDelegate {
gameWorld?.update(deltaTime: deltaTime)
}

func contactBegin(between nodeA: TFAnimatableNode, and nodeB: TFAnimatableNode) {
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?.handleContact(between: idA, and: idB)
gameWorld?.contactDidBegin(between: idA, and: idB)
}

func contactEnd(between nodeA: TFAnimatableNode, and nodeB: TFAnimatableNode) {
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?.handleSeparation(between: idA, and: idB)
gameWorld?.contactDidEnd(between: idA, and: idB)
}
}

Expand Down Expand Up @@ -90,6 +90,7 @@ extension GameViewController: SceneManagerDelegate {
view.ignoresSiblingOrder = true // to render nodes more efficiently
view.showsFPS = true
view.showsNodeCount = true
view.showsPhysics = true
}
}
}
34 changes: 30 additions & 4 deletions TowerForge/TowerForge/GameWorld.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class GameWorld {
private var selectionNode: UnitSelectionNode
private var grid: Grid
private var renderer: Renderer?
private var entitiesInContact: Set<TFContact> = []

init(scene: GameScene?) {
self.scene = scene
Expand All @@ -29,6 +30,10 @@ class GameWorld {
}

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

systemManager.update(deltaTime)
eventManager.executeEvents(in: self)
entityManager.update(deltaTime)
Expand All @@ -39,16 +44,37 @@ class GameWorld {
selectionNode.unitNodeDidSpawn(location)
}

func handleContact(between idA: UUID, and idB: UUID) {
guard let entityA = entityManager.entity(with: idA), let entityB = entityManager.entity(with: idB),
let event = entityA.collide(with: entityB) else {
// 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))
}

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)
}

func handleSeparation(between idA: UUID, and idB: UUID) {
private func handleSeparation(between idA: UUID, and idB: UUID) {
guard let entityA = entityManager.entity(with: idA), let entityB = entityManager.entity(with: idB) else {
return
}
Expand Down
18 changes: 18 additions & 0 deletions TowerForge/TowerForge/LevelManager/Collision/TFContact.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// TFContact.swift
// TowerForge
//
// Created by Zheng Ze on 22/3/24.
//

import Foundation

struct TFContact: Hashable {
let entityIdA: UUID
let entityIdB: UUID

static func == (lhs: TFContact, rhs: TFContact) -> Bool {
(lhs.entityIdA == rhs.entityIdA && lhs.entityIdB == rhs.entityIdB)
|| (lhs.entityIdA == rhs.entityIdB && lhs.entityIdB == rhs.entityIdA)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,47 +24,22 @@ class DamageComponent: TFComponent {
super.init()
}

override func update(deltaTime: TimeInterval) {
super.update(deltaTime: deltaTime)
var canDamage: Bool {
CACurrentMediaTime() - lastAttackTime >= attackRate
}

// Required components for the current Melee
guard let entity = entity,
let spriteComponent = entity.component(ofType: SpriteComponent.self) else {
return
func damage(_ healthComponent: HealthComponent) -> DamageEvent? {
guard canDamage, let entityId = healthComponent.entity?.id else {
return nil
}

// TODO: Shift damage logic to damage event and handled by health system.
// Loop opposite team's entities
// for entity in entityManager.entities {
// guard let playerComponent = entity.component(ofType: PlayerComponent.self) else {
// return
// }
// if playerComponent.player == .ownPlayer {
// return
// }
// // Get opposite team's components
// guard let oppositeSpriteComponent = entity.component(ofType: SpriteComponent.self),
// let oppositeHealthComponent = entity.component(ofType: HealthComponent.self) else {
// return
// }
//
// // Check collision with opposite team sprite component
// if oppositeSpriteComponent.node
// .calculateAccumulatedFrame().intersects(
// spriteComponent.node.calculateAccumulatedFrame()) {
//
// // Check if can attack
// if CACurrentMediaTime() - lastAttackTime > attackRate {
// lastAttackTime = CACurrentMediaTime()
// oppositeHealthComponent.decreaseHealth(amount: attackPower)
// }
//
// }
//
// // If only used once, then remove from entity
// if temporary {
// entityManager.removeEntity(with: entity.id)
// }
// }

guard let teamA = self.entity?.component(ofType: PlayerComponent.self)?.player,
let teamB = healthComponent.entity?.component(ofType: PlayerComponent.self)?.player,
teamA != teamB else {
return nil
}

lastAttackTime = CACurrentMediaTime()
return DamageEvent(on: entityId, at: lastAttackTime, with: attackPower)
}
}
5 changes: 2 additions & 3 deletions TowerForge/TowerForge/LevelManager/Entities/Arrow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,9 @@ class Arrow: BaseProjectile {
}

override func collide(with healthComponent: HealthComponent) -> TFEvent? {
guard let entityId = healthComponent.entity?.id,
let damageComponent = self.component(ofType: DamageComponent.self) else {
guard let damageComponent = self.component(ofType: DamageComponent.self) else {
return nil
}
return DamageEvent(on: entityId, at: Date().timeIntervalSince1970, with: damageComponent.attackPower)
return damageComponent.damage(healthComponent)
}
}
7 changes: 3 additions & 4 deletions TowerForge/TowerForge/LevelManager/Entities/MeleeUnit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class MeleeUnit: BaseUnit, Spawnable {
static let maxHealth = 100.0
static let damage = 10.0
static var cost = 10
static let attackRate = 10.0
static let attackRate = 1.0
static let velocity = CGVector(dx: 10.0, dy: 0.0)

required init(position: CGPoint, entityManager: EntityManager, team: Team) {
Expand Down Expand Up @@ -45,10 +45,9 @@ class MeleeUnit: BaseUnit, Spawnable {
}

override func collide(with healthComponent: HealthComponent) -> (any TFEvent)? {
guard let entityId = healthComponent.entity?.id,
let damageComponent = self.component(ofType: DamageComponent.self) else {
guard let damageComponent = self.component(ofType: DamageComponent.self) else {
return nil
}
return DamageEvent(on: entityId, at: Date().timeIntervalSince1970, with: damageComponent.attackPower)
return damageComponent.damage(healthComponent)
}
}
4 changes: 2 additions & 2 deletions TowerForge/TowerForge/SceneUpdateDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ import Foundation
protocol SceneUpdateDelegate: AnyObject {
func update(deltaTime: TimeInterval)
func touch(at location: CGPoint)
func contactBegin(between nodeA: TFAnimatableNode, and nodeB: TFAnimatableNode)
func contactEnd(between nodeA: TFAnimatableNode, and nodeB: TFAnimatableNode)
func contactBegin(between nodeA: TFSpriteNode, and nodeB: TFSpriteNode)
func contactEnd(between nodeA: TFSpriteNode, and nodeB: TFSpriteNode)
}
8 changes: 4 additions & 4 deletions TowerForge/TowerForge/Scenes/GameScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ class GameScene: SKScene {

extension GameScene: SKPhysicsContactDelegate {
public func didBegin(_ contact: SKPhysicsContact) {
guard let nodeA = contact.bodyA.node as? TFAnimatableNode,
let nodeB = contact.bodyB.node as? TFAnimatableNode else {
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? TFAnimatableNode,
let nodeB = contact.bodyB.node as? TFAnimatableNode else {
guard let nodeA = contact.bodyA.node as? TFSpriteNode,
let nodeB = contact.bodyB.node as? TFSpriteNode else {
return
}

Expand Down
2 changes: 1 addition & 1 deletion TowerForge/TowerForge/TFCore/TFSpriteNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ 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))
Expand Down

0 comments on commit cacc6ce

Please sign in to comment.