Skip to content

Commit

Permalink
Merge pull request #98 from Ark-Kit/tests/physics-kit
Browse files Browse the repository at this point in the history
[ark-physics-kit] tests and abstracting classes
  • Loading branch information
markusyeo authored Apr 21, 2024
2 parents a38cb88 + 2c2bd3f commit 4e2732f
Show file tree
Hide file tree
Showing 23 changed files with 773 additions and 86 deletions.
84 changes: 76 additions & 8 deletions ArkKit.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@ protocol AbstractArkPhysicsBody {
var categoryBitMask: UInt32 { get set }
var collisionBitMask: UInt32 { get set }
var contactTestBitMask: UInt32 { get set }

func applyImpulse(_ impulse: CGVector)
func applyAngularImpulse(_ impulse: CGFloat)
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import SpriteKit

class ArkSKPhysicsBody: AbstractArkPhysicsBody {
class ArkSKPhysicsBody: AbstractArkSKPhysicsBody {
private(set) var node: SKNode
private let nodeNoPhysicsBodyFailureMessage = "SKNode does not contain an associated SKPhysicsBody."
private let physicsBodyFailedToCreateMessage = "Failed to create an SKPhysicsBody."

init(rectangleOf size: CGSize, at position: CGPoint = .zero) {
let physicsBody = SKPhysicsBody(rectangleOf: size)
Expand All @@ -19,11 +20,20 @@ class ArkSKPhysicsBody: AbstractArkPhysicsBody {
}

init(polygonOf vertices: [CGPoint], at position: CGPoint = .zero) {
guard vertices.count >= 3 else {
self.node = SKNode()
assertionFailure(physicsBodyFailedToCreateMessage)
return
}

node = SKNode()
node.position = position

let cgPath = CGMutablePath()
cgPath.addLines(between: vertices)
cgPath.closeSubpath()

let physicsBody = SKPhysicsBody(polygonFrom: cgPath)
node = SKNode()
node.position = position
node.physicsBody = physicsBody
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Foundation

class ArkSKPhysicsBodyFactory: AbstractArkSKPhysicsBodyFactory {
func createCirclePhysicsBody(for entity: Entity, radius: CGFloat,
at position: CGPoint) -> any AbstractArkSKPhysicsBody {
ArkSKPhysicsBody(circleOf: radius, at: position)
}

func createRectanglePhysicsBody(for entity: Entity, size: CGSize,
at position: CGPoint) -> any AbstractArkSKPhysicsBody {
ArkSKPhysicsBody(rectangleOf: size, at: position)
}

func createPolygonPhysicsBody(for entity: Entity, vertices: [CGPoint],
at position: CGPoint) -> any AbstractArkSKPhysicsBody {
ArkSKPhysicsBody(polygonOf: vertices, at: position)
}
}

This file was deleted.

27 changes: 27 additions & 0 deletions ArkKit/ark-physics-kit/sprite-kit-physics-facade/BaseSKScene.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import SpriteKit

class BaseSKScene: AbstractSKScene {
override func sceneDidLoad() {
super.sceneDidLoad()
physicsWorld.gravity = CGVector(dx: 0, dy: -9.81)
physicsWorld.contactDelegate = self
}
}

extension BaseSKScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
guard let entityA = gameScene?.getEntity(for: contact.bodyA),
let entityB = gameScene?.getEntity(for: contact.bodyB) else {
return
}
sceneContactUpdateDelegate?.didContactBegin(between: entityA, and: entityB)
}

func didEnd(_ contact: SKPhysicsContact) {
guard let entityA = gameScene?.getEntity(for: contact.bodyA),
let entityB = gameScene?.getEntity(for: contact.bodyB) else {
return
}
sceneContactUpdateDelegate?.didContactEnd(between: entityA, and: entityB)
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import SpriteKit

class SKPhysicsBodyManager {
private(set) var entityToPhysicsBodyMap: [Entity: ArkSKPhysicsBody] = [:]
private(set) var entityToPhysicsBodyMap: [Entity: any AbstractArkSKPhysicsBody] = [:]
private var nodeToEntityMap: [SKNode: Entity] = [:]

func addBody(for entity: Entity, body: ArkSKPhysicsBody) -> Bool {
func addBody(for entity: Entity, body: any AbstractArkSKPhysicsBody) -> Bool {
guard entityToPhysicsBodyMap[entity] == nil, nodeToEntityMap[body.node] == nil else {
assertionFailure("Entity or Node already exists.")
return false
Expand All @@ -21,27 +21,29 @@ class SKPhysicsBodyManager {
nodeToEntityMap.removeValue(forKey: body.node)
}

func getBody(for entity: Entity) -> ArkSKPhysicsBody? {
func getBody(for entity: Entity) -> (any AbstractArkSKPhysicsBody)? {
entityToPhysicsBodyMap[entity]
}

func getEntity(for node: SKNode) -> Entity? {
nodeToEntityMap[node]
}

func applyImpulse(_ impulse: CGVector, to entity: Entity) {
@discardableResult
func applyImpulse(_ impulse: CGVector, to entity: Entity) -> Bool {
guard let body = entityToPhysicsBodyMap[entity] else {
assertionFailure("Entity does not exist.")
return
return false
}
body.applyImpulse(impulse)
return true
}

func applyAngularImpulse(_ angularImpulse: CGFloat, to entity: Entity) {
@discardableResult
func applyAngularImpulse(_ angularImpulse: CGFloat, to entity: Entity) -> Bool {
guard let body = entityToPhysicsBodyMap[entity] else {
assertionFailure("Entity does not exist.")
return
return false
}
body.applyAngularImpulse(angularImpulse)
return true
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
import SpriteKit

class SKPhysicsScene: AbstractArkPhysicsScene {
private(set) var basePhysicsScene: AbstractSKScene
private var physicsBodyManager: SKPhysicsBodyManager
private var physicsBodyFactory: AbstractArkSKPhysicsBodyFactory

init(size: CGSize, delegate: SKSceneDelegate? = nil,
basePhysicsScene: AbstractSKScene? = nil,
physicsBodyManager: SKPhysicsBodyManager? = nil,
gameScene: SKPhysicsScene? = nil,
physicsBodyFactory: AbstractArkSKPhysicsBodyFactory? = nil) {
self.basePhysicsScene = basePhysicsScene ?? BaseSKScene(size: size)
self.basePhysicsScene.delegate = delegate
self.physicsBodyManager = physicsBodyManager ?? SKPhysicsBodyManager()
self.physicsBodyFactory = physicsBodyFactory ?? ArkSKPhysicsBodyFactory()
self.basePhysicsScene.gameScene = gameScene ?? self
}

func getCurrentTime() -> TimeInterval {
basePhysicsScene.currentTime
}
Expand All @@ -9,9 +25,6 @@ class SKPhysicsScene: AbstractArkPhysicsScene {
basePhysicsScene.physicsWorld.gravity = gravity
}

private(set) var basePhysicsScene: BaseSKPhysicsScene
private var physicsBodyManager: SKPhysicsBodyManager

var sceneContactUpdateDelegate: ArkPhysicsContactUpdateDelegate? {
get { basePhysicsScene.sceneContactUpdateDelegate }
set { basePhysicsScene.sceneContactUpdateDelegate = newValue }
Expand All @@ -22,14 +35,6 @@ class SKPhysicsScene: AbstractArkPhysicsScene {
set { basePhysicsScene.sceneUpdateLoopDelegate = newValue }
}

init(size: CGSize, delegate: SKSceneDelegate? = nil) {
self.basePhysicsScene = BaseSKPhysicsScene(size: size)
self.basePhysicsScene.delegate = delegate
self.physicsBodyManager = SKPhysicsBodyManager()
self.basePhysicsScene.gameScene = self

}

func getDeltaTime() -> TimeInterval {
basePhysicsScene.deltaTime
}
Expand All @@ -43,23 +48,23 @@ class SKPhysicsScene: AbstractArkPhysicsScene {
func createCirclePhysicsBody(for entity: Entity,
withRadius radius: CGFloat,
at position: CGPoint) -> AbstractArkPhysicsBody {
let newPhysicsBody = ArkSKPhysicsBody(circleOf: radius, at: position)
let newPhysicsBody = physicsBodyFactory.createCirclePhysicsBody(for: entity, radius: radius, at: position)
addBody(for: entity, bodyToAdd: newPhysicsBody)
return newPhysicsBody
}

func createRectanglePhysicsBody(for entity: Entity,
withSize size: CGSize,
at position: CGPoint) -> AbstractArkPhysicsBody {
let newPhysicsBody = ArkSKPhysicsBody(rectangleOf: size, at: position)
let newPhysicsBody = physicsBodyFactory.createRectanglePhysicsBody(for: entity, size: size, at: position)
addBody(for: entity, bodyToAdd: newPhysicsBody)
return newPhysicsBody
}

func createPolygonPhysicsBody(for entity: Entity,
withVertices vertices: [CGPoint],
at position: CGPoint) -> any AbstractArkPhysicsBody {
let newPhysicsBody = ArkSKPhysicsBody(polygonOf: vertices, at: position)
let newPhysicsBody = physicsBodyFactory.createPolygonPhysicsBody(for: entity, vertices: vertices, at: position)
addBody(for: entity, bodyToAdd: newPhysicsBody)
return newPhysicsBody
}
Expand All @@ -83,7 +88,7 @@ class SKPhysicsScene: AbstractArkPhysicsScene {
physicsBodyManager.applyAngularImpulse(angularImpulse, to: entity)
}

func addBody(for entity: Entity, bodyToAdd: ArkSKPhysicsBody) {
func addBody(for entity: Entity, bodyToAdd: any AbstractArkSKPhysicsBody) {
if physicsBodyManager.addBody(for: entity, body: bodyToAdd) {
basePhysicsScene.addChild(bodyToAdd.node)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import SpriteKit

protocol AbstractArkSKPhysicsBody: AbstractArkPhysicsBody {
var node: SKNode { get }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Foundation

protocol AbstractArkSKPhysicsBodyFactory {
func createCirclePhysicsBody(for entity: Entity, radius: CGFloat,
at position: CGPoint) -> any AbstractArkSKPhysicsBody
func createRectanglePhysicsBody(for entity: Entity, size: CGSize,
at position: CGPoint) -> any AbstractArkSKPhysicsBody
func createPolygonPhysicsBody(for entity: Entity, vertices: [CGPoint],
at position: CGPoint) -> any AbstractArkSKPhysicsBody
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import SpriteKit

protocol AbstractSKSceneProtocol: AnyObject {
var gameScene: SKPhysicsScene? { get set }
var sceneContactUpdateDelegate: ArkPhysicsContactUpdateDelegate? { get set }
var sceneUpdateLoopDelegate: ArkPhysicsSceneUpdateLoopDelegate? { get set }
var currentTime: TimeInterval { get set }
var deltaTime: TimeInterval { get }

func update(_ currentTime: TimeInterval)
}

class AbstractSKScene: SKScene, AbstractSKSceneProtocol {
weak var gameScene: SKPhysicsScene?
weak var sceneContactUpdateDelegate: ArkPhysicsContactUpdateDelegate?
weak var sceneUpdateLoopDelegate: ArkPhysicsSceneUpdateLoopDelegate?

var currentTime: TimeInterval = 0
private var lastUpdateTime: TimeInterval = 0
var deltaTime: TimeInterval = 0

func calculateDeltaTime(from currentTime: TimeInterval) -> TimeInterval {
if lastUpdateTime.isZero {
lastUpdateTime = currentTime
}
let deltaTime = currentTime - lastUpdateTime
lastUpdateTime = currentTime
return deltaTime
}

override func update(_ currentTime: TimeInterval) {
deltaTime = calculateDeltaTime(from: currentTime)
self.currentTime = currentTime
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import XCTest
@testable import ArkKit

class AbstractArkPhysicsBodyTests: XCTestCase {
var physicsBody: MockArkSKPhysicsBody!

override func setUp() {
super.setUp()
physicsBody = MockArkSKPhysicsBody()
}

func testPropertySettings() {
physicsBody.mass = 10.0
XCTAssertEqual(physicsBody.mass, 10.0, "Mass should be settable and match the expected value.")
}

func testApplyLinearImpulse() {
let impulse = CGVector(dx: 15, dy: 20)
physicsBody.applyImpulse(impulse)
XCTAssertEqual(physicsBody.velocity, impulse, "Applied linear impulse should affect the velocity correctly.")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import XCTest
@testable import ArkKit

class AbstractArkPhysicsSceneTests: XCTestCase {
var scene: MockArkPhysicsScene!
var entity: Entity!
var physicsBody: AbstractArkPhysicsBody!

override func setUp() {
super.setUp()
scene = MockArkPhysicsScene()
entity = Entity()
physicsBody = scene.createCirclePhysicsBody(for: entity, withRadius: 20.0, at: .zero)
}

func testCreateCirclePhysicsBody() {
let radius: CGFloat = 5.0
let position = CGPoint(x: 100, y: 100)
_ = scene.createCirclePhysicsBody(for: entity, withRadius: radius, at: position)
XCTAssertNotNil(scene.getPhysicsBody(for: entity), "Physics body should be created and retrievable.")
}

func testApplyImpulse() {
let impulse = CGVector(dx: 10, dy: 10)
scene.apply(impulse: impulse, to: entity)
XCTAssertEqual(physicsBody.velocity, impulse, "Impulse should be applied correctly to the body's velocity.")
}

func testSetGravity() {
let gravity = CGVector(dx: 0, dy: -9.8)
scene.setGravity(gravity)
}
}
2 changes: 0 additions & 2 deletions ArkKitTests/ark-physics-kit-tests/ArkPhysicsKitTests.swift

This file was deleted.

Loading

0 comments on commit 4e2732f

Please sign in to comment.