Skip to content

Commit

Permalink
Fix bug where contact is only detected once. Update damage event gene…
Browse files Browse the repository at this point in the history
…ration to respect the component's attack rate.
  • Loading branch information
zheng-ze committed Mar 22, 2024
1 parent 4ce1cbd commit 25a2553
Show file tree
Hide file tree
Showing 10 changed files with 82 additions and 23 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 @@ -117,6 +118,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 @@ -256,6 +258,7 @@
isa = PBXGroup;
children = (
3CE951552BACA0CF008B2785 /* Collidable.swift */,
3CE951572BAD724D008B2785 /* TFContact.swift */,
);
path = Collision;
sourceTree = "<group>";
Expand Down Expand Up @@ -607,6 +610,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 @@ -14,6 +14,7 @@ class GameWorld {
private var eventManager: EventManager
private var selectionNode: UnitSelectionNode
private var renderer: Renderer?
private var entitiesInContact: Set<TFContact> = []

init(scene: GameScene?) {
self.scene = scene
Expand All @@ -27,6 +28,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 @@ -37,16 +42,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,6 +24,18 @@ class DamageComponent: TFComponent {
super.init()
}

var canDamage: Bool {
CACurrentMediaTime() - lastAttackTime >= attackRate
}

func damage(_ healthComponent: HealthComponent) -> DamageEvent? {
guard canDamage, let entityId = healthComponent.entity?.id else {
return nil
}
lastAttackTime = CACurrentMediaTime()
return DamageEvent(on: entityId, at: lastAttackTime, with: attackPower)
}

override func update(deltaTime: TimeInterval) {
super.update(deltaTime: deltaTime)

Expand All @@ -32,7 +44,7 @@ class DamageComponent: TFComponent {
let spriteComponent = entity.component(ofType: SpriteComponent.self) else {
return
}

// TODO: Shift damage logic to damage event and handled by health system.
// Loop opposite team's entities
// for entity in entityManager.entities {
Expand Down
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 25a2553

Please sign in to comment.