From f6277a7b270908acf20a885ce63fd0bcafdaf5cc Mon Sep 17 00:00:00 2001 From: Markus Yeo Date: Tue, 16 Apr 2024 01:44:27 +0800 Subject: [PATCH 1/2] [ark-physics-kit] feat: separate sync to and update systems --- ArkKit.xcodeproj/project.pbxproj | 12 +- .../app/utils/set-up/ArkSetUpStrategy.swift | 18 +- .../ArkPhysicsSyncSystem.swift | 185 ++++++++++++++++++ ...tem.swift => ArkPhysicsUpdateSystem.swift} | 144 +------------- 4 files changed, 208 insertions(+), 151 deletions(-) create mode 100644 ArkKit/ark-physics-kit/ArkPhysicsSyncSystem.swift rename ArkKit/ark-physics-kit/{ArkPhysicsSystem.swift => ArkPhysicsUpdateSystem.swift} (56%) diff --git a/ArkKit.xcodeproj/project.pbxproj b/ArkKit.xcodeproj/project.pbxproj index 82ca43d..c41c7d0 100644 --- a/ArkKit.xcodeproj/project.pbxproj +++ b/ArkKit.xcodeproj/project.pbxproj @@ -146,7 +146,7 @@ 02E0E8F42BA283180043E2BA /* UIKitRect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E0E8F32BA283180043E2BA /* UIKitRect.swift */; }; 02E0E8F62BA283940043E2BA /* UIKitPolygon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E0E8F52BA283940043E2BA /* UIKitPolygon.swift */; }; 02E0E8F82BA284540043E2BA /* UIKitBitmap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E0E8F72BA284540043E2BA /* UIKitBitmap.swift */; }; - 280CD3B72BA7391100372C5D /* ArkPhysicsSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 280CD3B62BA7391100372C5D /* ArkPhysicsSystem.swift */; }; + 280CD3B72BA7391100372C5D /* ArkPhysicsUpdateSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 280CD3B62BA7391100372C5D /* ArkPhysicsUpdateSystem.swift */; }; 280CD3B92BA7391700372C5D /* PhysicsComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 280CD3B82BA7391700372C5D /* PhysicsComponent.swift */; }; 280CD3BC2BA73CF900372C5D /* ArkSKPhysicsBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 280CD3BB2BA73CF900372C5D /* ArkSKPhysicsBody.swift */; }; 280CD3C42BA7419400372C5D /* ArkPhysicsContactUpdateDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 280CD3C32BA7419400372C5D /* ArkPhysicsContactUpdateDelegate.swift */; }; @@ -165,6 +165,7 @@ 282248532BA82E5800850D7F /* SKPhysicsBodyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 282248522BA82E5800850D7F /* SKPhysicsBodyManager.swift */; }; 28548F502BCD9CA700C49404 /* ArkEntityRemovalSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28548F4F2BCD9CA700C49404 /* ArkEntityRemovalSystem.swift */; }; 28548F522BCD9CB900C49404 /* ToRemoveComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28548F512BCD9CB900C49404 /* ToRemoveComponent.swift */; }; + 28548F542BCD9EA000C49404 /* ArkPhysicsSyncSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28548F532BCD9EA000C49404 /* ArkPhysicsSyncSystem.swift */; }; 286C09C02BADD0BB000343B1 /* TankGameMapBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 286C09BF2BADD0BB000343B1 /* TankGameMapBuilder.swift */; }; 286C09C22BADD5FB000343B1 /* TankGameTerrainObjectBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 286C09C12BADD5FB000343B1 /* TankGameTerrainObjectBuilder.swift */; }; 287B00552BC16E6C002F0114 /* TankHPComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287B00542BC16E6C002F0114 /* TankHPComponent.swift */; }; @@ -434,7 +435,7 @@ 02E0E8F32BA283180043E2BA /* UIKitRect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitRect.swift; sourceTree = ""; }; 02E0E8F52BA283940043E2BA /* UIKitPolygon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitPolygon.swift; sourceTree = ""; }; 02E0E8F72BA284540043E2BA /* UIKitBitmap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBitmap.swift; sourceTree = ""; }; - 280CD3B62BA7391100372C5D /* ArkPhysicsSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkPhysicsSystem.swift; sourceTree = ""; }; + 280CD3B62BA7391100372C5D /* ArkPhysicsUpdateSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkPhysicsUpdateSystem.swift; sourceTree = ""; }; 280CD3B82BA7391700372C5D /* PhysicsComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhysicsComponent.swift; sourceTree = ""; }; 280CD3BB2BA73CF900372C5D /* ArkSKPhysicsBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkSKPhysicsBody.swift; sourceTree = ""; }; 280CD3C32BA7419400372C5D /* ArkPhysicsContactUpdateDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkPhysicsContactUpdateDelegate.swift; sourceTree = ""; }; @@ -453,6 +454,7 @@ 282248522BA82E5800850D7F /* SKPhysicsBodyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SKPhysicsBodyManager.swift; sourceTree = ""; }; 28548F4F2BCD9CA700C49404 /* ArkEntityRemovalSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkEntityRemovalSystem.swift; sourceTree = ""; }; 28548F512BCD9CB900C49404 /* ToRemoveComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToRemoveComponent.swift; sourceTree = ""; }; + 28548F532BCD9EA000C49404 /* ArkPhysicsSyncSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkPhysicsSyncSystem.swift; sourceTree = ""; }; 286C09BF2BADD0BB000343B1 /* TankGameMapBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankGameMapBuilder.swift; sourceTree = ""; }; 286C09C12BADD5FB000343B1 /* TankGameTerrainObjectBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankGameTerrainObjectBuilder.swift; sourceTree = ""; }; 287B00542BC16E6C002F0114 /* TankHPComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankHPComponent.swift; sourceTree = ""; }; @@ -1151,7 +1153,8 @@ children = ( 280CD3D72BA7FA9A00372C5D /* ark-physics-facade */, 280CD3CC2BA74E3100372C5D /* sprite-kit-physics-facade */, - 280CD3B62BA7391100372C5D /* ArkPhysicsSystem.swift */, + 280CD3B62BA7391100372C5D /* ArkPhysicsUpdateSystem.swift */, + 28548F532BCD9EA000C49404 /* ArkPhysicsSyncSystem.swift */, 280CD3B82BA7391700372C5D /* PhysicsComponent.swift */, 28844EA02BA881E60037A7F6 /* DemoPhysicsComponent.swift */, ); @@ -1946,6 +1949,7 @@ 02B3C64E2BCD8507002331A0 /* ArkPlayerStateSetupDelegate.swift in Sources */, 945441042BC9178400E90ECE /* SnakeGame.swift in Sources */, ADA847FD2BBC4B5500B19378 /* AbstractNetworkService.swift in Sources */, + 28548F542BCD9EA000C49404 /* ArkPhysicsSyncSystem.swift in Sources */, 02C3951E2BA849490075F1CA /* CircleRenderableComponent.swift in Sources */, 9479A3AF2BA952E300F99013 /* AbstractShape.swift in Sources */, 02E0E8EE2BA280BD0043E2BA /* UIKitShape.swift in Sources */, @@ -2091,7 +2095,7 @@ AD787A842B9C6C78003EBBD0 /* Component.swift in Sources */, 945441252BC99D8800E90ECE /* SnakeGridPositionComponent.swift in Sources */, 8F5573BF2BA425E4007030C8 /* ShapeRenderable.swift in Sources */, - 280CD3B72BA7391100372C5D /* ArkPhysicsSystem.swift in Sources */, + 280CD3B72BA7391100372C5D /* ArkPhysicsUpdateSystem.swift in Sources */, 28EFCB252BCCA7CC0059A908 /* ArkECSWrapper.swift in Sources */, 280CD3D62BA7F8CC00372C5D /* SKPhysicsScene.swift in Sources */, 941BE21E2BC0EC1900707997 /* ArkProtocol.swift in Sources */, diff --git a/ArkKit/app/utils/set-up/ArkSetUpStrategy.swift b/ArkKit/app/utils/set-up/ArkSetUpStrategy.swift index cef150e..e053795 100644 --- a/ArkKit/app/utils/set-up/ArkSetUpStrategy.swift +++ b/ArkKit/app/utils/set-up/ArkSetUpStrategy.swift @@ -132,25 +132,29 @@ extension ArkSetUpStrategy { let simulator = SKSimulator(size: CGSize(width: worldWidth, height: worldHeight)) ark.gameLoop = simulator - let physicsSystem = ArkPhysicsSystem(simulator: simulator, - eventManager: ark.arkState.eventManager, - arkECS: ark.arkState.arkECS) + let physicsUpdateSystem = ArkPhysicsUpdateSystem(simulator: simulator, + eventManager: ark.arkState.eventManager, + arkECS: ark.arkState.arkECS) let animationSystem = ArkAnimationSystem() let canvasSystem = ArkCanvasSystem() let timeSystem = ArkTimeSystem() let cameraSystem = ArkCameraSystem() let entityRemovalSystem = ArkEntityRemovalSystem() ark.arkState.arkECS.addSystem(timeSystem) - ark.arkState.arkECS.addSystem(physicsSystem) + ark.arkState.arkECS.addSystem(physicsUpdateSystem) ark.arkState.arkECS.addSystem(animationSystem) ark.arkState.arkECS.addSystem(canvasSystem) ark.arkState.arkECS.addSystem(cameraSystem) ark.arkState.arkECS.addSystem(entityRemovalSystem) // inject dependency into game loop - simulator.physicsScene?.sceneContactUpdateDelegate = physicsSystem - simulator.physicsScene?.sceneUpdateLoopDelegate = physicsSystem - ark.gameLoop?.updatePhysicsSceneDelegate = physicsSystem + let physicsSyncSystem = ArkPhysicsSyncSystem(simulator: simulator, + eventManager: ark.arkState.eventManager, + arkECS: ark.arkState.arkECS) + ark.arkState.arkECS.addSystem(physicsSyncSystem) + simulator.physicsScene?.sceneContactUpdateDelegate = physicsSyncSystem + simulator.physicsScene?.sceneUpdateLoopDelegate = physicsSyncSystem + ark.gameLoop?.updatePhysicsSceneDelegate = physicsSyncSystem } func setupMultiplayerGameLoop() { diff --git a/ArkKit/ark-physics-kit/ArkPhysicsSyncSystem.swift b/ArkKit/ark-physics-kit/ArkPhysicsSyncSystem.swift new file mode 100644 index 0000000..7851f64 --- /dev/null +++ b/ArkKit/ark-physics-kit/ArkPhysicsSyncSystem.swift @@ -0,0 +1,185 @@ +import Foundation + +/** + * A system which syncs from the physics world. + */ +class ArkPhysicsSyncSystem: UpdateSystem { + var active: Bool + var scene: AbstractArkPhysicsScene? + var eventManager: ArkEventManager + var arkECS: ArkECS + weak var sceneUpdateDelegate: ArkPhysicsContactUpdateDelegate? + + init(simulator: AbstractPhysicsArkSimulator, + eventManager: ArkEventManager, + arkECS: ArkECS, + active: Bool = true) { + self.active = active + self.scene = simulator.physicsScene + self.eventManager = eventManager + self.arkECS = arkECS + } + + func update(deltaTime: TimeInterval, arkECS: ArkECS) { + } + + private func getPhysicsComponents(_ arkECS: ArkECS) -> [(Entity, PhysicsComponent)] { + arkECS.getEntities(with: [PhysicsComponent.self]).compactMap { entity in + guard let physicsComponent = arkECS.getComponent(ofType: PhysicsComponent.self, for: entity) else { + return nil + } + return (entity, physicsComponent) + } + } + + func syncFromPhysicsEngine() { + let syncStrategies: [ComponentSyncing] = [PhysicsComponentSync(), + PositionComponentSync(), + RotationComponentSync()] + + scene?.forEachEntity(perform: { entity, physicsBody in + syncStrategies.forEach { strategy in + strategy.sync(entity: entity, with: physicsBody, using: self.arkECS) + } + }) + } + + // MARK: Impulse application + func apply(impulse: CGVector, to entity: Entity, arkECS: ArkECS) { + guard var physicsComponent: PhysicsComponent = + arkECS.getComponent(ofType: PhysicsComponent.self, for: entity) else { + return + } + physicsComponent.impulse = impulse + arkECS.upsertComponent(physicsComponent, to: entity) + } + + func apply(angularImpulse: CGFloat, to entity: Entity, arkECS: ArkECS) { + guard var physicsComponent: PhysicsComponent = + arkECS.getComponent(ofType: PhysicsComponent.self, for: entity) else { + return + } + physicsComponent.angularImpulse = angularImpulse + arkECS.upsertComponent(physicsComponent, to: entity) + } + + // MARK: Handle Collision + func handleCollisionBegan(between entityA: Entity, and entityB: Entity) { + let arkCollisionEvent = makeCollisionEvent(ArkCollisionBeganEvent.self, entityA, entityB) + self.eventManager.emit(arkCollisionEvent) + } + + func handleCollisionEnd(between entityA: Entity, and entityB: Entity) { + let arkCollisionEvent = makeCollisionEvent(ArkCollisionEndedEvent.self, entityA, entityB) + self.eventManager.emit(arkCollisionEvent) + } + + private func makeCollisionEvent (_ eventType: T.Type, + _ entityA: Entity, + _ entityB: Entity) -> T { + let entityACategoryBitMask = scene?.getPhysicsBody(for: entityA)?.categoryBitMask ?? 0 + let entityBCategoryBitMask = scene?.getPhysicsBody(for: entityB)?.categoryBitMask ?? 0 + let arkCollisionEvent = T(eventData: ArkCollisionEventData( + entityA: entityA, + entityACategoryBitMask: entityACategoryBitMask, + entityB: entityB, + entityBCategoryBitMask: entityBCategoryBitMask), + priority: 0) + return arkCollisionEvent + } +} + +extension ArkPhysicsSyncSystem: ArkPhysicsContactUpdateDelegate { + func didContactBegin(between entityA: Entity, and entityB: Entity) { + handleCollisionBegan(between: entityA, and: entityB) + } + + func didContactEnd(between entityA: Entity, and entityB: Entity) { + handleCollisionEnd(between: entityA, and: entityB) + } +} + +extension ArkPhysicsSyncSystem: ArkPhysicsSceneUpdateLoopDelegate { + func update(_ deltaTime: TimeInterval) { + syncFromPhysicsEngine() + } +} + +protocol ArkCollisionEventProtocol: ArkEvent { + init(eventData: ArkCollisionEventData, priority: Int?) +} + +struct ArkCollisionEventData: ArkEventData { + var name = "" + var entityA: Entity + var entityACategoryBitMask: UInt32 + var entityB: Entity + var entityBCategoryBitMask: UInt32 +} + +struct ArkCollisionEndedEvent: ArkCollisionEventProtocol { + static var id = UUID() + var eventData: ArkCollisionEventData + var priority: Int? + + init(eventData: ArkCollisionEventData, priority: Int? = 10) { + self.eventData = eventData + } +} + +struct ArkCollisionBeganEvent: ArkCollisionEventProtocol { + static var id = UUID() + var eventData: ArkCollisionEventData + var priority: Int? + + init(eventData: ArkCollisionEventData, priority: Int? = 10) { + self.eventData = eventData + self.priority = priority + } +} + +protocol ComponentSyncing { + func sync(entity: Entity, with physicsBody: AbstractArkPhysicsBody, using arkECS: ArkECS) +} + +struct PhysicsComponentSync: ComponentSyncing { + func sync(entity: Entity, with physicsBody: AbstractArkPhysicsBody, using arkECS: ArkECS) { + guard var physicsComponent: PhysicsComponent = + arkECS.getComponent(ofType: PhysicsComponent.self, for: entity) else { + return } + physicsComponent.velocity = physicsBody.velocity + physicsComponent.mass = physicsBody.mass + physicsComponent.affectedByGravity = physicsBody.affectedByGravity + physicsComponent.linearDamping = physicsBody.linearDamping + physicsComponent.isDynamic = physicsBody.isDynamic + physicsComponent.allowsRotation = physicsBody.allowsRotation + physicsComponent.restitution = physicsBody.restitution + physicsComponent.friction = physicsBody.friction + + physicsComponent.categoryBitMask = physicsBody.categoryBitMask + physicsComponent.contactTestBitMask = physicsBody.contactTestBitMask + physicsComponent.collisionBitMask = physicsBody.collisionBitMask + arkECS.upsertComponent(physicsComponent, to: entity) + } +} + +struct PositionComponentSync: ComponentSyncing { + func sync(entity: Entity, with physicsBody: AbstractArkPhysicsBody, using arkECS: ArkECS) { + guard let positionComponent: PositionComponent = + arkECS.getComponent(ofType: PositionComponent.self, for: entity) else { + return } + var newPositionComp = positionComponent + newPositionComp.position = physicsBody.position + arkECS.upsertComponent(newPositionComp, to: entity) + } +} + +struct RotationComponentSync: ComponentSyncing { + func sync(entity: Entity, with physicsBody: AbstractArkPhysicsBody, using arkECS: ArkECS) { + guard var rotationComponent: RotationComponent = + arkECS.getComponent(ofType: RotationComponent.self, for: entity) else { + return } + rotationComponent.angleInRadians = physicsBody.zRotation + arkECS.upsertComponent(rotationComponent, to: entity) + } +} diff --git a/ArkKit/ark-physics-kit/ArkPhysicsSystem.swift b/ArkKit/ark-physics-kit/ArkPhysicsUpdateSystem.swift similarity index 56% rename from ArkKit/ark-physics-kit/ArkPhysicsSystem.swift rename to ArkKit/ark-physics-kit/ArkPhysicsUpdateSystem.swift index 3668f32..4953136 100644 --- a/ArkKit/ark-physics-kit/ArkPhysicsSystem.swift +++ b/ArkKit/ark-physics-kit/ArkPhysicsUpdateSystem.swift @@ -1,22 +1,19 @@ import Foundation /** - * A system which updates all running animation instances in ArkECS after the given delta. + * A system which updates all running physics instances in ArkECS after the given delta. */ -class ArkPhysicsSystem: UpdateSystem { +class ArkPhysicsUpdateSystem: UpdateSystem { var active: Bool - var simulator: AbstractPhysicsArkSimulator var scene: AbstractArkPhysicsScene? var eventManager: ArkEventManager var arkECS: ArkECS - weak var sceneUpdateDelegate: ArkPhysicsContactUpdateDelegate? init(simulator: AbstractPhysicsArkSimulator, eventManager: ArkEventManager, arkECS: ArkECS, active: Bool = true) { self.active = active - self.simulator = simulator self.scene = simulator.physicsScene self.eventManager = eventManager self.arkECS = arkECS @@ -36,25 +33,13 @@ class ArkPhysicsSystem: UpdateSystem { } } - func syncFromPhysicsEngine() { - let syncStrategies: [ComponentSyncing] = [PhysicsComponentSync(), - PositionComponentSync(), - RotationComponentSync()] - - scene?.forEachEntity(perform: { entity, physicsBody in - syncStrategies.forEach { strategy in - strategy.sync(entity: entity, with: physicsBody, using: self.arkECS) - } - }) - } - func syncToPhysicsEngine(_ physicsComponents: [(Entity, PhysicsComponent)], arkECS: ArkECS) { for (entity, physics) in physicsComponents { handlePhysicsComponentRemovalIfNeeded(for: entity, using: physics, arkECS: arkECS) if let toRemoveComponent = arkECS.getComponent(ofType: ToRemoveComponent.self, for: entity), toRemoveComponent.toBeRemoved { - return } + return } guard let positionComponent = arkECS.getComponent(ofType: PositionComponent.self, for: entity), let rotationComponent = arkECS.getComponent(ofType: RotationComponent.self, for: entity) else { @@ -73,10 +58,9 @@ class ArkPhysicsSystem: UpdateSystem { arkECS: ArkECS) { guard let toRemoveComponent = arkECS.getComponent(ofType: ToRemoveComponent.self, for: entity), toRemoveComponent.toBeRemoved else { - return } + return } scene?.removePhysicsBody(for: entity) - arkECS.removeEntity(entity) } private func syncPhysicsBody(for entity: Entity, position: PositionComponent, @@ -169,124 +153,4 @@ class ArkPhysicsSystem: UpdateSystem { physicsComponent.angularImpulse = angularImpulse arkECS.upsertComponent(physicsComponent, to: entity) } - - // MARK: Handle Collision - func handleCollisionBegan(between entityA: Entity, and entityB: Entity) { - var arkCollisionEvent = makeCollisionEvent(ArkCollisionBeganEvent.self, entityA, entityB) - self.eventManager.emit(arkCollisionEvent) - } - - func handleCollisionEnd(between entityA: Entity, and entityB: Entity) { - var arkCollisionEvent = makeCollisionEvent(ArkCollisionEndedEvent.self, entityA, entityB) - self.eventManager.emit(arkCollisionEvent) - } - - private func makeCollisionEvent (_ eventType: T.Type, - _ entityA: Entity, - _ entityB: Entity) -> T { - let entityACategoryBitMask = scene?.getPhysicsBody(for: entityA)?.categoryBitMask ?? 0 - let entityBCategoryBitMask = scene?.getPhysicsBody(for: entityB)?.categoryBitMask ?? 0 - let arkCollisionEvent = T(eventData: ArkCollisionEventData( - entityA: entityA, - entityACategoryBitMask: entityACategoryBitMask, - entityB: entityB, - entityBCategoryBitMask: entityBCategoryBitMask), - priority: 0) - return arkCollisionEvent - } -} - -extension ArkPhysicsSystem: ArkPhysicsContactUpdateDelegate { - func didContactBegin(between entityA: Entity, and entityB: Entity) { - handleCollisionBegan(between: entityA, and: entityB) - } - - func didContactEnd(between entityA: Entity, and entityB: Entity) { - handleCollisionEnd(between: entityA, and: entityB) - } -} - -extension ArkPhysicsSystem: ArkPhysicsSceneUpdateLoopDelegate { - func update(_ deltaTime: TimeInterval) { - syncFromPhysicsEngine() - } -} - -protocol ArkCollisionEventProtocol: ArkEvent { - init(eventData: ArkCollisionEventData, priority: Int?) -} - -struct ArkCollisionEventData: ArkEventData { - var name = "" - var entityA: Entity - var entityACategoryBitMask: UInt32 - var entityB: Entity - var entityBCategoryBitMask: UInt32 -} - -struct ArkCollisionEndedEvent: ArkCollisionEventProtocol { - static var id = UUID() - var eventData: ArkCollisionEventData - var priority: Int? - - init(eventData: ArkCollisionEventData, priority: Int? = 10) { - self.eventData = eventData - } -} - -struct ArkCollisionBeganEvent: ArkCollisionEventProtocol { - static var id = UUID() - var eventData: ArkCollisionEventData - var priority: Int? - - init(eventData: ArkCollisionEventData, priority: Int? = 10) { - self.eventData = eventData - self.priority = priority - } -} - -protocol ComponentSyncing { - func sync(entity: Entity, with physicsBody: AbstractArkPhysicsBody, using arkECS: ArkECS) -} - -struct PhysicsComponentSync: ComponentSyncing { - func sync(entity: Entity, with physicsBody: AbstractArkPhysicsBody, using arkECS: ArkECS) { - guard var physicsComponent: PhysicsComponent = - arkECS.getComponent(ofType: PhysicsComponent.self, for: entity) else { - return } - physicsComponent.velocity = physicsBody.velocity - physicsComponent.mass = physicsBody.mass - physicsComponent.affectedByGravity = physicsBody.affectedByGravity - physicsComponent.linearDamping = physicsBody.linearDamping - physicsComponent.isDynamic = physicsBody.isDynamic - physicsComponent.allowsRotation = physicsBody.allowsRotation - physicsComponent.restitution = physicsBody.restitution - physicsComponent.friction = physicsBody.friction - - physicsComponent.categoryBitMask = physicsBody.categoryBitMask - physicsComponent.contactTestBitMask = physicsBody.contactTestBitMask - physicsComponent.collisionBitMask = physicsBody.collisionBitMask - arkECS.upsertComponent(physicsComponent, to: entity) - } -} - -struct PositionComponentSync: ComponentSyncing { - func sync(entity: Entity, with physicsBody: AbstractArkPhysicsBody, using arkECS: ArkECS) { - guard let positionComponent: PositionComponent = - arkECS.getComponent(ofType: PositionComponent.self, for: entity) else { - return } - var newPositionComp = positionComponent - newPositionComp.position = physicsBody.position - arkECS.upsertComponent(newPositionComp, to: entity) - } -} - -struct RotationComponentSync: ComponentSyncing { - func sync(entity: Entity, with physicsBody: AbstractArkPhysicsBody, using arkECS: ArkECS) { - guard var rotationComponent: RotationComponent = - arkECS.getComponent(ofType: RotationComponent.self, for: entity) else { - return } - rotationComponent.angleInRadians = physicsBody.zRotation - arkECS.upsertComponent(rotationComponent, to: entity) - } } From defc21289e65485701a72b980ac14b60fba032f7 Mon Sep 17 00:00:00 2001 From: Markus Yeo Date: Tue, 16 Apr 2024 01:54:58 +0800 Subject: [PATCH 2/2] [ark-physics-kit] feat: strategy pattern for physics body creation --- .../ArkPhysicsUpdateSystem.swift | 60 ++++++++++++++----- .../DemoPhysicsComponent.swift | 2 +- ArkKit/ark-physics-kit/PhysicsComponent.swift | 13 ++-- 3 files changed, 52 insertions(+), 23 deletions(-) diff --git a/ArkKit/ark-physics-kit/ArkPhysicsUpdateSystem.swift b/ArkKit/ark-physics-kit/ArkPhysicsUpdateSystem.swift index 4953136..374f219 100644 --- a/ArkKit/ark-physics-kit/ArkPhysicsUpdateSystem.swift +++ b/ArkKit/ark-physics-kit/ArkPhysicsUpdateSystem.swift @@ -9,6 +9,12 @@ class ArkPhysicsUpdateSystem: UpdateSystem { var eventManager: ArkEventManager var arkECS: ArkECS + var creators: [ArkPhysicsShape: PhysicsBodyCreator] = [ + .circle: CirclePhysicsBodyCreator(), + .rectangle: RectanglePhysicsBodyCreator(), + .polygon: PolygonPhysicsBodyCreator() + ] + init(simulator: AbstractPhysicsArkSimulator, eventManager: ArkEventManager, arkECS: ArkECS, @@ -90,22 +96,12 @@ class ArkPhysicsUpdateSystem: UpdateSystem { positionComponent: PositionComponent, rotationComponent: RotationComponent, physicsComponent: PhysicsComponent) { - var physicsBody: AbstractArkPhysicsBody? - if physicsComponent.shape == .circle, let radius = physicsComponent.radius { - physicsBody = scene?.createCirclePhysicsBody(for: entity, - withRadius: radius, - at: positionComponent.position) - } else if physicsComponent.shape == .rectangle, let size = physicsComponent.size { - physicsBody = scene?.createRectanglePhysicsBody(for: entity, - withSize: size, - at: positionComponent.position) - } else if physicsComponent.shape == .polygon, let vertices = physicsComponent.vertices { - physicsBody = scene?.createPolygonPhysicsBody(for: entity, - withVertices: vertices, - at: positionComponent.position) - } - - if var physicsBody = physicsBody { + guard let creator = creators[physicsComponent.shape] else { + return } + let position = positionComponent.position + if let scene = scene, + var physicsBody = creator.createPhysicsBody(for: entity, with: physicsComponent, + at: position, scene: scene) { updatePhysicsBody(&physicsBody, position: positionComponent, rotation: rotationComponent, physics: physicsComponent) } @@ -154,3 +150,35 @@ class ArkPhysicsUpdateSystem: UpdateSystem { arkECS.upsertComponent(physicsComponent, to: entity) } } + +protocol PhysicsBodyCreator { + func createPhysicsBody(for entity: Entity, with component: PhysicsComponent, + at position: CGPoint, scene: AbstractArkPhysicsScene) -> AbstractArkPhysicsBody? +} + +struct CirclePhysicsBodyCreator: PhysicsBodyCreator { + func createPhysicsBody(for entity: Entity, with component: PhysicsComponent, + at position: CGPoint, scene: AbstractArkPhysicsScene) -> AbstractArkPhysicsBody? { + guard let radius = component.radius else { + return nil } + return scene.createCirclePhysicsBody(for: entity, withRadius: radius, at: position) + } +} + +struct RectanglePhysicsBodyCreator: PhysicsBodyCreator { + func createPhysicsBody(for entity: Entity, with component: PhysicsComponent, + at position: CGPoint, scene: AbstractArkPhysicsScene) -> AbstractArkPhysicsBody? { + guard let size = component.size else { + return nil } + return scene.createRectanglePhysicsBody(for: entity, withSize: size, at: position) + } +} + +struct PolygonPhysicsBodyCreator: PhysicsBodyCreator { + func createPhysicsBody(for entity: Entity, with component: PhysicsComponent, + at position: CGPoint, scene: AbstractArkPhysicsScene) -> AbstractArkPhysicsBody? { + guard let vertices = component.vertices else { + return nil } + return scene.createPolygonPhysicsBody(for: entity, withVertices: vertices, at: position) + } +} diff --git a/ArkKit/ark-physics-kit/DemoPhysicsComponent.swift b/ArkKit/ark-physics-kit/DemoPhysicsComponent.swift index 1158408..acae793 100644 --- a/ArkKit/ark-physics-kit/DemoPhysicsComponent.swift +++ b/ArkKit/ark-physics-kit/DemoPhysicsComponent.swift @@ -27,7 +27,7 @@ struct DemoPhysicsCategory { static let coin: UInt32 = 0x1 << 2 // Binary 100 } -let demoDummyShape = PhysicsComponent.Shape.circle +let demoDummyShape = ArkPhysicsShape.circle let demoDummyRadius = CGFloat(10) /// If I want to create an enemy that passes through a player and a coin but has a contact emitted with a player diff --git a/ArkKit/ark-physics-kit/PhysicsComponent.swift b/ArkKit/ark-physics-kit/PhysicsComponent.swift index f580c95..6ce5fd9 100644 --- a/ArkKit/ark-physics-kit/PhysicsComponent.swift +++ b/ArkKit/ark-physics-kit/PhysicsComponent.swift @@ -1,14 +1,15 @@ import Foundation +enum ArkPhysicsShape: Codable { + case circle + case rectangle + case polygon +} + struct PhysicsComponent: SendableComponent { - enum Shape: Codable { - case circle - case rectangle - case polygon - } // A physics component can either have a size or a radius depending on the shape - let shape: Shape + let shape: ArkPhysicsShape var size: CGSize? var radius: CGFloat? var vertices: [CGPoint]?