Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Renderer and attempt to generalise UnitGenerator #18

Merged
merged 10 commits into from
Mar 22, 2024
12 changes: 12 additions & 0 deletions TowerForge/TowerForge.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
3CE9514B2BAC83FA008B2785 /* SpawnableEntities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE9514A2BAC83FA008B2785 /* SpawnableEntities.swift */; };
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 */; };
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 @@ -115,6 +116,7 @@
3CE9514A2BAC83FA008B2785 /* SpawnableEntities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpawnableEntities.swift; sourceTree = "<group>"; };
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>"; };
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 @@ -250,6 +252,14 @@
path = Rendering;
sourceTree = "<group>";
};
3CE951542BACA079008B2785 /* Collision */ = {
isa = PBXGroup;
children = (
3CE951552BACA0CF008B2785 /* Collidable.swift */,
);
path = Collision;
sourceTree = "<group>";
};
5200624C2BA8D574000DBA30 /* GameComponents */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -371,6 +381,7 @@
52DF5FDB2BA32CEF00135367 /* LevelManager */ = {
isa = PBXGroup;
children = (
3CE951542BACA079008B2785 /* Collision */,
5200624F2BA8D9E4000DBA30 /* Generators */,
3C9955C32BA585CD00D33FA5 /* Systems */,
3C9955B82BA5620A00D33FA5 /* Events */,
Expand Down Expand Up @@ -612,6 +623,7 @@
3C9955C82BA5865C00D33FA5 /* ConcurrentEvent.swift in Sources */,
3C9955AD2BA483B100D33FA5 /* TFSystem.swift in Sources */,
3C9955BE2BA57E4B00D33FA5 /* EventManager.swift in Sources */,
3CE951562BACA0CF008B2785 /* Collidable.swift in Sources */,
52DF5FE62BA33AF300135367 /* TFSpriteNode.swift in Sources */,
3C9955B42BA4B12000D33FA5 /* ArrowTower.swift in Sources */,
52DF5FE92BA33F9700135367 /* Animatable.swift in Sources */,
Expand Down
16 changes: 16 additions & 0 deletions TowerForge/TowerForge/GameViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,22 @@ extension GameViewController: SceneUpdateDelegate {
func update(deltaTime: TimeInterval) {
gameWorld?.update(deltaTime: deltaTime)
}

func contactBegin(between nodeA: TFAnimatableNode, and nodeB: TFAnimatableNode) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking if we couldjust generalise it to the TFSpriteNode instead of AnimatableNode? and also changing from contactBegin to contactDidBegin

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep sure ill change it in my next pr.

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

func contactEnd(between nodeA: TFAnimatableNode, and nodeB: TFAnimatableNode) {
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)
}
}

extension GameViewController: SceneManagerDelegate {
Expand Down
17 changes: 16 additions & 1 deletion TowerForge/TowerForge/GameWorld.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,23 @@ class GameWorld {
selectionNode.unitNodeDidSpawn(location)
}

private func setUpSelectionNode() {
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 {
return
}

eventManager.add(event)
}

func handleSeparation(between idA: UUID, and idB: UUID) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a need to implement this? since we don't have a separation logic if the units come into contact with each other but perhaps for future powerups this can be a good one.

guard let entityA = entityManager.entity(with: idA), let entityB = entityManager.entity(with: idB) else {
return
}
// TODO: Handle any separation logic here.
}

private func setUpSelectionNode() {
selectionNode.delegate = self
scene?.addChild(selectionNode)
// Position unit selection node on the left side of the screen
Expand Down
14 changes: 14 additions & 0 deletions TowerForge/TowerForge/LevelManager/Collision/Collidable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// Collidable.swift
// TowerForge
//
// Created by Zheng Ze on 22/3/24.
//

import Foundation

protocol Collidable {
func collide(with other: Collidable) -> TFEvent?
func collide(with damageComponent: DamageComponent) -> TFEvent?
func collide(with healthComponent: HealthComponent) -> TFEvent?
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,23 @@ 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 {
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
// 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()) {
Expand All @@ -59,11 +60,11 @@ class DamageComponent: TFComponent {
// }
//
// }

// If only used once, then remove from entity
if temporary {
entityManager.removeEntity(with: entity.id)
}
}
//
// // If only used once, then remove from entity
// if temporary {
// entityManager.removeEntity(with: entity.id)
// }
// }
}
}
15 changes: 15 additions & 0 deletions TowerForge/TowerForge/LevelManager/Entities/Arrow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,19 @@ class Arrow: BaseProjectile {
temporary: true,
entityManager: entityManager))
}

override func collide(with other: any Collidable) -> TFEvent? {
guard let damageComponent = self.component(ofType: DamageComponent.self) else {
return nil
}
return other.collide(with: damageComponent)
}

override func collide(with healthComponent: HealthComponent) -> TFEvent? {
guard let entityId = healthComponent.entity?.id,
let damageComponent = self.component(ofType: DamageComponent.self) else {
return nil
}
return DamageEvent(on: entityId, at: Date().timeIntervalSince1970, with: damageComponent.attackPower)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class BaseProjectile: TFEntity {
animatableKey: key)
self.addComponent(spriteComponent)
}

private func createPositionComponent(position: CGPoint) {
let positionComponent = PositionComponent(position: position)
self.addComponent(positionComponent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,26 @@ class BaseTower: TFEntity {
createPositionComponent(position: position)
}

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

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

override func collide(with damageComponent: DamageComponent) -> TFEvent? {
guard let healthComponent = self.component(ofType: HealthComponent.self) else {
return nil
}

// No call to super here as super is done on collide with Collidable above.
return DamageEvent(on: self.id, at: Date().timeIntervalSince1970, with: damageComponent.attackPower)
}

private func createHealthComponent(maxHealth: CGFloat, entityManager: EntityManager) {
let healthComponent = HealthComponent(maxHealth: maxHealth, entityManager: entityManager)
self.addComponent(healthComponent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,14 @@ enum UnitType {
}

class BaseUnit: TFEntity {
var cost: Int
init(textureNames: [String],
size: CGSize,
key: String,
position: CGPoint,
maxHealth: CGFloat,
entityManager: EntityManager,
cost: Int,
velocity: CGVector,
team: Team) {
self.cost = cost
super.init()
createHealthComponent(maxHealth: maxHealth, entityManager: entityManager)
createSpriteComponent(textureNames: textureNames, size: size, key: key, position: position)
Expand All @@ -52,6 +49,26 @@ class BaseUnit: TFEntity {
createPlayerComponent(team: team)
}

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

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

override func collide(with damageComponent: DamageComponent) -> TFEvent? {
guard let healthComponent = self.component(ofType: HealthComponent.self) else {
return nil
}

// No call to super here as super is done on collide with Collidable above.
return DamageEvent(on: self.id, at: Date().timeIntervalSince1970, with: damageComponent.attackPower)
}

private func createHealthComponent(maxHealth: CGFloat, entityManager: EntityManager) {
let healthComponent = HealthComponent(maxHealth: maxHealth, entityManager: entityManager)
self.addComponent(healthComponent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ class MeleeUnit: BaseUnit, Spawnable {
position: position,
maxHealth: MeleeUnit.maxHealth,
entityManager: entityManager,
cost: MeleeUnit.cost,
velocity: MeleeUnit.velocity,
team: team)
self.addComponent(DamageComponent(attackRate: MeleeUnit.attackRate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ class SoldierUnit: BaseUnit, Spawnable {
position: position,
maxHealth: SoldierUnit.maxHealth,
entityManager: entityManager,
cost: SoldierUnit.cost,
velocity: SoldierUnit.velocity,
team: team)

Expand Down
7 changes: 5 additions & 2 deletions TowerForge/TowerForge/LevelManager/Events/TFEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ protocol TFEvent {
}

extension TFEvent {
func concurrentlyWith(_ otherEvent: TFEvent) -> TFEvent {
ConcurrentEvent(self, otherEvent)
func concurrentlyWith(_ otherEvent: TFEvent?) -> TFEvent {
guard let otherEvent = otherEvent else {
return self
}
return ConcurrentEvent(self, otherEvent)
}
}
15 changes: 14 additions & 1 deletion TowerForge/TowerForge/LevelManager/TFEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

class TFEntity {
class TFEntity: Collidable {
let id: UUID
private(set) var components: [UUID: TFComponent]

Expand Down Expand Up @@ -45,4 +45,17 @@ class TFEntity {
componentToBeRemoved.willRemoveFromEntity()
components.removeValue(forKey: componentToBeRemoved.id)
}

// To be overriden by sub classes as needed
func collide(with other: any Collidable) -> TFEvent? {
nil
}

func collide(with damageComponent: DamageComponent) -> TFEvent? {
nil
}

func collide(with healthComponent: HealthComponent) -> TFEvent? {
nil
}
}
3 changes: 2 additions & 1 deletion TowerForge/TowerForge/Rendering/Renderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Renderer {

private var renderedNodes: [UUID: TFAnimatableNode] = [:]

init(target: Renderable, scene: SKScene?) {
init(target: Renderable, scene: GameScene?) {
self.target = target
self.scene = scene
}
Expand Down Expand Up @@ -57,6 +57,7 @@ class Renderer {
width: spriteComponent.width,
animatableKey: spriteComponent.animatableKey)
node.position = positionComponent.position
node.name = entity.id.uuidString
renderedNodes[entity.id] = node
scene?.addChild(node)
}
Expand Down
2 changes: 2 additions & 0 deletions TowerForge/TowerForge/SceneUpdateDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ import Foundation
protocol SceneUpdateDelegate: AnyObject {
func update(deltaTime: TimeInterval)
func touch(at location: CGPoint)
func contactBegin(between nodeA: TFAnimatableNode, and nodeB: TFAnimatableNode)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above, perhaps changing TFAnimatableNode to TFSpriteNode?

func contactEnd(between nodeA: TFAnimatableNode, and nodeB: TFAnimatableNode)
}
26 changes: 25 additions & 1 deletion TowerForge/TowerForge/Scenes/GameScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import GameplayKit
class GameScene: SKScene {
private var lastUpdatedTimeInterval = TimeInterval(0)
unowned var updateDelegate: SceneUpdateDelegate?
var sceneManagerDelegate: SceneManagerDelegate?
unowned var sceneManagerDelegate: SceneManagerDelegate?

override func sceneDidLoad() {
super.sceneDidLoad()
physicsWorld.contactDelegate = self
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
Expand All @@ -31,3 +36,22 @@ class GameScene: SKScene {
updateDelegate?.update(deltaTime: changeInTime)
}
}

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

updateDelegate?.contactEnd(between: nodeA, and: nodeB)
}
}
Loading