diff --git a/SDPhysicsEngine/.gitignore b/SDPhysicsEngine/.gitignore new file mode 100644 index 00000000..0023a534 --- /dev/null +++ b/SDPhysicsEngine/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/SDPhysicsEngine/Package.swift b/SDPhysicsEngine/Package.swift new file mode 100644 index 00000000..c75546f0 --- /dev/null +++ b/SDPhysicsEngine/Package.swift @@ -0,0 +1,23 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "SDPhysicsEngine", + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "SDPhysicsEngine", + targets: ["SDPhysicsEngine"]) + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "SDPhysicsEngine"), + .testTarget( + name: "SDPhysicsEngineTests", + dependencies: ["SDPhysicsEngine"]) + ] +) diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift new file mode 100644 index 00000000..a258c3d1 --- /dev/null +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift @@ -0,0 +1,26 @@ +import SpriteKit + +public class GameScene: SKScene, SDScene { + + public var sceneDelegate: SDSceneDelegate? + + private var lastUpdateTime: TimeInterval? + + override public func update(_ currentTime: TimeInterval) { + super.update(currentTime) + + guard let lastUpdateTime = lastUpdateTime else { + lastUpdateTime = currentTime + return + } + + let deltaTime = currentTime - lastUpdateTime + self.lastUpdateTime = currentTime + + sceneDelegate?.update(self, deltaTime: deltaTime) + } + + public func addObject(_ object: SDObject) { + addChild(object.node) + } +} diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDObject.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDObject.swift new file mode 100644 index 00000000..7eb44ce6 --- /dev/null +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDObject.swift @@ -0,0 +1,37 @@ +import SpriteKit + +public class SDObject { + let node: SKNode + + var innerRotation: CGFloat = 0 + + public init() { + node = SKNode() + } + + init(node: SKNode) { + self.node = node + } + + public var position: CGPoint { + get { node.position } + set { node.position = newValue } + } + + public var zPosition: CGFloat { + get { node.zPosition } + set { node.zPosition = newValue } + } + + public var rotation: CGFloat { + get { innerRotation } + set { + innerRotation = newValue + node.run(SKAction.rotate(toAngle: newValue, duration: 1)) + } + } + + public var physicsBody: SDPhysicsBody? { + willSet { node.physicsBody = newValue?.body } + } +} diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDPhysicsBody.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDPhysicsBody.swift new file mode 100644 index 00000000..b3f26a22 --- /dev/null +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDPhysicsBody.swift @@ -0,0 +1,39 @@ +import SpriteKit + +public class SDPhysicsBody { + let body: SKPhysicsBody + + public init(rectangleOf size: CGSize) { + body = SKPhysicsBody(rectangleOf: size) + } + + public init(circleOf radius: CGFloat) { + body = SKPhysicsBody(circleOfRadius: radius) + } + + public var mass: CGFloat { + get { body.mass } + set { body.mass = newValue } + } + + public var velocity: CGVector { + get { body.velocity } + set { body.velocity = newValue } + } + + public var force: CGVector { + // get { body.force } + // set { body.force = newValue } + CGVector(dx: 0, dy: 0) + } + + public var affectedByGravity: Bool { + get { body.affectedByGravity } + set { body.affectedByGravity = newValue } + } + + public var isDynamic: Bool { + get { body.isDynamic } + set { body.isDynamic = newValue } + } +} diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDSpriteObject.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDSpriteObject.swift new file mode 100644 index 00000000..f6954771 --- /dev/null +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDSpriteObject.swift @@ -0,0 +1,35 @@ +import SpriteKit + +public class SDSpriteObject: SDObject { + let spriteNode: SKSpriteNode + + public var activeTexture: String? + + public init(imageNamed: String) { + self.spriteNode = SKSpriteNode(imageNamed: imageNamed) + super.init(node: spriteNode) + } + + public var size: CGSize { + get { spriteNode.size } + set { spriteNode.size = newValue } + } + + public func runTexture(named: String) { + let texture = loadTexture(named: named) + spriteNode.run(SKAction.repeatForever( + SKAction.animate(with: texture, timePerFrame: TimeInterval(0.1), resize: false, restore: true) + )) + + activeTexture = named + } + + private func loadTexture(named: String) -> [SKTexture] { + let textureAtlas = SKTextureAtlas(named: named) + var frames = [SKTexture]() + for idx in 0..(ofType type: T.Type, of entityId: EntityId) -> T? + func entity(of: EntityId) -> Entity? +} diff --git a/star-dash/star-dash/GameBridge/GameBridge.swift b/star-dash/star-dash/GameBridge/GameBridge.swift new file mode 100644 index 00000000..815171f2 --- /dev/null +++ b/star-dash/star-dash/GameBridge/GameBridge.swift @@ -0,0 +1,85 @@ +import SDPhysicsEngine + +class GameBridge { + + var entityManager: EntitySyncInterface + var scene: SDScene + + var entitiesMap: [EntityId: SDObject] + var modules: [SyncModule] + var creationModule: CreationModule? + + init(entityManager: EntitySyncInterface, scene: SDScene) { + self.entityManager = entityManager + self.scene = scene + modules = [] + entitiesMap = [:] + + registerModules() + } + + func syncFromEntities() { + var toRemove = Set(entitiesMap.keys) + + for entity in entityManager.entities { + toRemove.remove(entity.id) + if let object = entitiesMap[entity.id] { + update(object: object, from: entity) + } else { + createObject(from: entity) + } + } + + for entityId in toRemove { + removeObject(from: entityId) + } + } + + func syncToEntities() { + for (entityId, object) in entitiesMap { + if let entity = entityManager.entity(of: entityId) { + update(entity: entity, from: object) + } + } + } + + private func registerModule(_ module: SyncModule) { + modules.append(module) + } + + private func registerModules() { + let spriteModule = SpriteModule(entityManager: entityManager) + self.creationModule = spriteModule + + registerModule(spriteModule) + registerModule(ObjectModule(entityManager: entityManager)) + registerModule(PhysicsModule(entityManager: entityManager)) + } + + private func update(entity: Entity, from object: SDObject) { + modules.forEach { + $0.sync(entity: entity, from: object) + } + } + + private func update(object: SDObject, from entity: Entity) { + modules.forEach { + $0.sync(object: object, from: entity) + } + } + + private func createObject(from entity: Entity) { + guard let newObject = creationModule?.createObject(from: entity) else { + return + } + entitiesMap[entity.id] = newObject + + modules.forEach { + $0.create(for: newObject, from: entity) + } + self.scene.addObject(newObject) + } + + private func removeObject(from entityId: EntityId) { + } +} diff --git a/star-dash/star-dash/GameBridge/SyncModule/CreationModule.swift b/star-dash/star-dash/GameBridge/SyncModule/CreationModule.swift new file mode 100644 index 00000000..73cabd4d --- /dev/null +++ b/star-dash/star-dash/GameBridge/SyncModule/CreationModule.swift @@ -0,0 +1,5 @@ +import SDPhysicsEngine + +protocol CreationModule { + func createObject(from entity: Entity) -> SDObject? +} diff --git a/star-dash/star-dash/GameBridge/SyncModule/ObjectModule.swift b/star-dash/star-dash/GameBridge/SyncModule/ObjectModule.swift new file mode 100644 index 00000000..49c540a6 --- /dev/null +++ b/star-dash/star-dash/GameBridge/SyncModule/ObjectModule.swift @@ -0,0 +1,31 @@ +import CoreGraphics +import SDPhysicsEngine + +class ObjectModule: SyncModule { + let entityManager: EntitySyncInterface + + init(entityManager: EntitySyncInterface) { + self.entityManager = entityManager + } + + func sync(entity: Entity, from object: SDObject) { + guard let positionComponent = entityManager.component(ofType: PositionComponent.self, of: entity.id) else { + return + } + + positionComponent.setPosition(position: object.position) + positionComponent.setRotation(rotation: object.rotation) + } + + func sync(object: SDObject, from entity: Entity) { + guard let positionComponent = entityManager.component(ofType: PositionComponent.self, of: entity.id) else { + return + } + + object.position = positionComponent.position + object.rotation = positionComponent.rotation + } + + func create(for object: SDObject, from entity: Entity) { + } +} diff --git a/star-dash/star-dash/GameBridge/SyncModule/PhysicsModule.swift b/star-dash/star-dash/GameBridge/SyncModule/PhysicsModule.swift new file mode 100644 index 00000000..21432119 --- /dev/null +++ b/star-dash/star-dash/GameBridge/SyncModule/PhysicsModule.swift @@ -0,0 +1,46 @@ +import CoreGraphics +import SDPhysicsEngine + +class PhysicsModule: SyncModule { + let entityManager: EntitySyncInterface + + init(entityManager: EntitySyncInterface) { + self.entityManager = entityManager + } + + func sync(entity: Entity, from object: SDObject) { + guard let physicsComponent = entityManager.component(ofType: PhysicsComponent.self, of: entity.id), + let body = object.physicsBody else { + return + } + + physicsComponent.mass = body.mass + physicsComponent.velocity = body.velocity + // physicsComponent.force = body.force + physicsComponent.affectedByGravity = body.affectedByGravity + } + + func sync(object: SDObject, from entity: Entity) { + guard let physicsComponent = entityManager.component(ofType: PhysicsComponent.self, of: entity.id), + let body = object.physicsBody else { + return + } + + body.mass = physicsComponent.mass + body.velocity = physicsComponent.velocity + // body.force = physicsComponent.force + body.affectedByGravity = physicsComponent.affectedByGravity + } + + func create(for object: SDObject, from entity: Entity) { + guard let physicsComponent = entityManager.component(ofType: PhysicsComponent.self, of: entity.id) else { + return + } + + object.physicsBody = createRectanglePhysicsBody(physicsComponent: physicsComponent) + } + + private func createRectanglePhysicsBody(physicsComponent: PhysicsComponent) -> SDPhysicsBody { + SDPhysicsBody(rectangleOf: CGSize(width: 50, height: 50)) + } +} diff --git a/star-dash/star-dash/GameBridge/SyncModule/SpriteModule.swift b/star-dash/star-dash/GameBridge/SyncModule/SpriteModule.swift new file mode 100644 index 00000000..a08c9705 --- /dev/null +++ b/star-dash/star-dash/GameBridge/SyncModule/SpriteModule.swift @@ -0,0 +1,47 @@ +import CoreGraphics +import SDPhysicsEngine + +class SpriteModule: SyncModule { + let entityManager: EntitySyncInterface + + init(entityManager: EntitySyncInterface) { + self.entityManager = entityManager + } + + func sync(entity: Entity, from object: SDObject) { + } + + func sync(object: SDObject, from entity: Entity) { + guard let spriteComponent = entityManager.component(ofType: SpriteComponent.self, of: entity.id), + let spriteObject = object as? SDSpriteObject else { + return + } + + // if spriteComponent.image != spriteObject.activeTexture { + // spriteObject.runTexture(named: spriteComponent.image) + // } + } + + func create(for object: SDObject, from entity: Entity) { + } +} + +extension SpriteModule: CreationModule { + func createObject(from entity: Entity) -> SDObject? { + var newObject = SDObject() + if let spriteComponent = entityManager.component(ofType: SpriteComponent.self, of: entity.id) { + let spriteObject = SDSpriteObject(imageNamed: "PlayerRedNose") + spriteObject.size = CGSize(width: 100, height: 140) + newObject = spriteObject + } + + guard let positionComponent = entityManager.component(ofType: PositionComponent.self, of: entity.id) else { + return nil + } + + newObject.position = positionComponent.position + newObject.rotation = CGFloat(positionComponent.rotation) + + return newObject + } +} diff --git a/star-dash/star-dash/GameBridge/SyncModule/SyncModule.swift b/star-dash/star-dash/GameBridge/SyncModule/SyncModule.swift new file mode 100644 index 00000000..e74df332 --- /dev/null +++ b/star-dash/star-dash/GameBridge/SyncModule/SyncModule.swift @@ -0,0 +1,8 @@ +import SDPhysicsEngine + +protocol SyncModule { + + func sync(entity: Entity, from object: SDObject) + func sync(object: SDObject, from entity: Entity) + func create(for object: SDObject, from entity: Entity) +} diff --git a/star-dash/star-dash/GameEngine/Components/PositionComponent.swift b/star-dash/star-dash/GameEngine/Components/PositionComponent.swift index 0c162dc7..12d83b14 100644 --- a/star-dash/star-dash/GameEngine/Components/PositionComponent.swift +++ b/star-dash/star-dash/GameEngine/Components/PositionComponent.swift @@ -9,15 +9,15 @@ import Foundation class PositionComponent: Component { var position: CGPoint - var rotation: Float + var rotation: CGFloat - init(id: UUID, entityId: UUID, position: CGPoint, rotation: Float) { + init(id: UUID, entityId: UUID, position: CGPoint, rotation: CGFloat) { self.position = position self.rotation = rotation super.init(id: id, entityId: entityId) } - convenience init(entityId: UUID, position: CGPoint, rotation: Float) { + convenience init(entityId: UUID, position: CGPoint, rotation: CGFloat) { self.init(id: UUID(), entityId: entityId, position: position, rotation: rotation) } @@ -25,7 +25,7 @@ class PositionComponent: Component { self.position = position } - func setRotation(rotation: Float) { + func setRotation(rotation: CGFloat) { self.rotation = rotation } } diff --git a/star-dash/star-dash/GameEngine/Entities/EntityManager.swift b/star-dash/star-dash/GameEngine/Entities/EntityManager.swift index 6ca87e28..7baff5ae 100644 --- a/star-dash/star-dash/GameEngine/Entities/EntityManager.swift +++ b/star-dash/star-dash/GameEngine/Entities/EntityManager.swift @@ -28,7 +28,7 @@ class EntityManager { } func add(component: Component) { - guard self.componentMap[component.id] != nil else { + guard self.componentMap[component.id] == nil else { return } self.componentMap[component.id] = component @@ -36,10 +36,11 @@ class EntityManager { } func add(entity: Entity) { - guard self.entityMap[entity.id] != nil else { + guard self.entityMap[entity.id] == nil else { return } self.entityMap[entity.id] = entity + self.entityComponentMap[entity.id] = Set() } func component(ofType type: T.Type, of entityId: EntityId) -> T? { diff --git a/star-dash/star-dash/GameEngine/GameEngine.swift b/star-dash/star-dash/GameEngine/GameEngine.swift index ac4c59d1..80bc3556 100644 --- a/star-dash/star-dash/GameEngine/GameEngine.swift +++ b/star-dash/star-dash/GameEngine/GameEngine.swift @@ -9,14 +9,13 @@ import Foundation class GameEngine { private let systemManager: SystemManager - private let entityManager: EntityManager + let entityManager: EntityManager // TODO: Set to private private let eventManager: EventManager - init(scene: GameScene) { + init() { self.systemManager = SystemManager() self.entityManager = EntityManager() self.eventManager = EventManager() - // TODO: link game engine to renderer setUpSystems() } @@ -35,3 +34,18 @@ class GameEngine { extension GameEngine: EventModifiable { // TODO: functions of event modifiable } + +extension GameEngine: EntitySyncInterface { + + var entities: [Entity] { + Array(entityManager.entityMap.values) + } + + func component(ofType type: T.Type, of entityId: EntityId) -> T? { + entityManager.component(ofType: type, of: entityId) + } + + func entity(of entityId: EntityId) -> Entity? { + entityManager.entityMap[entityId] + } +} diff --git a/star-dash/star-dash/GameEngine/Systems/PositionSystem.swift b/star-dash/star-dash/GameEngine/Systems/PositionSystem.swift index 56a98ceb..909bab9c 100644 --- a/star-dash/star-dash/GameEngine/Systems/PositionSystem.swift +++ b/star-dash/star-dash/GameEngine/Systems/PositionSystem.swift @@ -26,7 +26,7 @@ class PositionSystem: System { positionComponent.setPosition(position: newPosition) } - func rotate(entityId: EntityId, to newRotation: Float) { + func rotate(entityId: EntityId, to newRotation: CGFloat) { guard let positionComponent = getPositionComponent(of: entityId) else { return } @@ -34,7 +34,7 @@ class PositionSystem: System { positionComponent.setRotation(rotation: newRotation) } - func sync(entityPositionMap: [EntityId: CGPoint], entityRotationMap: [EntityId: Float]) { + func sync(entityPositionMap: [EntityId: CGPoint], entityRotationMap: [EntityId: CGFloat]) { for (entityId, newPosition) in entityPositionMap { move(entityId: entityId, to: newPosition) } diff --git a/star-dash/star-dash/GameScene/GameScene.swift b/star-dash/star-dash/GameScene/GameScene.swift deleted file mode 100644 index 7e3d9fbf..00000000 --- a/star-dash/star-dash/GameScene/GameScene.swift +++ /dev/null @@ -1,68 +0,0 @@ -import SpriteKit - -class GameScene: SKScene, SKPhysicsContactDelegate { - - var ball: SKNode? - var platform: SKNode? - - var camBall: SKCameraNode? - var camPlatform: SKCameraNode? - - override func update(_ currentTime: TimeInterval) { - super.update(currentTime) - - guard let ball = self.ball, - let platform = self.platform else { - return - } - - camBall?.position = ball.position - camPlatform?.position = platform.position - return - } - - override func touchesBegan(_ touches: Set, with event: UIEvent?) { - - } - - func setupGame() { - - self.physicsWorld.gravity = CGVector(dx: physicsWorld.gravity.dx, dy: physicsWorld.gravity.dy * 0.3) - - let background = SKSpriteNode(imageNamed: "GameBackground") - background.position = CGPoint(x: size.width / 2, y: size.height / 2) - background.zPosition = -1 - addChild(background) - - let ball = SKSpriteNode(imageNamed: "PlayerRedNose") - ball.size = CGSize(width: 100, height: 140) - ball.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 60, height: 110)) - ball.position = CGPoint(x: size.width / 2, y: size.height / 2 + 200) - self.ball = ball - - let textureAtlas = SKTextureAtlas(named: "PlayerRedNoseRun") - var frames = [SKTexture]() - for idx in 0..