From 65d1c006889b7b81eeea345fb145aa35e6bde50f Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Thu, 14 Mar 2024 20:58:43 +0800 Subject: [PATCH 01/21] chore: Add SDPhysicsEngine package --- SDPhysicsEngine/.gitignore | 8 ++ SDPhysicsEngine/Package.swift | 23 ++++ .../SDPhysicsEngine/SDPhysicsEngine.swift | 2 + .../SDPhysicsEngineTests.swift | 12 +++ star-dash/star-dash.xcodeproj/project.pbxproj | 102 ++++++++++-------- 5 files changed, 104 insertions(+), 43 deletions(-) create mode 100644 SDPhysicsEngine/.gitignore create mode 100644 SDPhysicsEngine/Package.swift create mode 100644 SDPhysicsEngine/Sources/SDPhysicsEngine/SDPhysicsEngine.swift create mode 100644 SDPhysicsEngine/Tests/SDPhysicsEngineTests/SDPhysicsEngineTests.swift 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..1aada652 --- /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/SDPhysicsEngine.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/SDPhysicsEngine.swift new file mode 100644 index 00000000..08b22b80 --- /dev/null +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/SDPhysicsEngine.swift @@ -0,0 +1,2 @@ +// The Swift Programming Language +// https://docs.swift.org/swift-book diff --git a/SDPhysicsEngine/Tests/SDPhysicsEngineTests/SDPhysicsEngineTests.swift b/SDPhysicsEngine/Tests/SDPhysicsEngineTests/SDPhysicsEngineTests.swift new file mode 100644 index 00000000..d51f45b3 --- /dev/null +++ b/SDPhysicsEngine/Tests/SDPhysicsEngineTests/SDPhysicsEngineTests.swift @@ -0,0 +1,12 @@ +import XCTest +@testable import SDPhysicsEngine + +final class SDPhysicsEngineTests: XCTestCase { + func testExample() throws { + // XCTest Documentation + // https://developer.apple.com/documentation/xctest + + // Defining Test Cases and Test Methods + // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods + } +} diff --git a/star-dash/star-dash.xcodeproj/project.pbxproj b/star-dash/star-dash.xcodeproj/project.pbxproj index 7b322b3b..05679b31 100644 --- a/star-dash/star-dash.xcodeproj/project.pbxproj +++ b/star-dash/star-dash.xcodeproj/project.pbxproj @@ -17,13 +17,6 @@ 4E630F0D2B9F7E090008F887 /* star_dashTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E630F0C2B9F7E090008F887 /* star_dashTests.swift */; }; 4E630F172B9F7E090008F887 /* star_dashUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E630F162B9F7E090008F887 /* star_dashUITests.swift */; }; 4E630F192B9F7E090008F887 /* star_dashUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E630F182B9F7E090008F887 /* star_dashUITestsLaunchTests.swift */; }; - E6A745162BA057040080C1BE /* MTKRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A745112BA057040080C1BE /* MTKRenderer.swift */; }; - E6A745172BA057040080C1BE /* ControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A745122BA057040080C1BE /* ControlView.swift */; }; - E6A745182BA057040080C1BE /* Renderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A745132BA057040080C1BE /* Renderer.swift */; }; - E6A745192BA057040080C1BE /* GameScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A745152BA057040080C1BE /* GameScene.swift */; }; - E6A7451B2BA0C1890080C1BE /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A7451A2BA0C1890080C1BE /* PlayerView.swift */; }; - E6A7451D2BA0CAD90080C1BE /* JoystickView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A7451C2BA0CAD90080C1BE /* JoystickView.swift */; }; - E6B550A12BA15E9C00DC7396 /* OverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6B550A02BA15E9C00DC7396 /* OverlayView.swift */; }; 4E630F272B9F7E770008F887 /* Entity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E630F262B9F7E770008F887 /* Entity.swift */; }; 4E630F2A2B9F7EF60008F887 /* PositionComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E630F292B9F7EF60008F887 /* PositionComponent.swift */; }; 4E630F2C2B9F7F460008F887 /* Component.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E630F2B2B9F7F460008F887 /* Component.swift */; }; @@ -38,6 +31,13 @@ 4E8660622BA0964A0035530D /* Obstacle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8660612BA0964A0035530D /* Obstacle.swift */; }; 4E8660642BA096600035530D /* Tool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8660632BA096600035530D /* Tool.swift */; }; 4E8660662BA097D40035530D /* PhysicsConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8660652BA097D40035530D /* PhysicsConstants.swift */; }; + E6A745162BA057040080C1BE /* MTKRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A745112BA057040080C1BE /* MTKRenderer.swift */; }; + E6A745172BA057040080C1BE /* ControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A745122BA057040080C1BE /* ControlView.swift */; }; + E6A745182BA057040080C1BE /* Renderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A745132BA057040080C1BE /* Renderer.swift */; }; + E6A745192BA057040080C1BE /* GameScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A745152BA057040080C1BE /* GameScene.swift */; }; + E6A7451B2BA0C1890080C1BE /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A7451A2BA0C1890080C1BE /* PlayerView.swift */; }; + E6A7451D2BA0CAD90080C1BE /* JoystickView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A7451C2BA0CAD90080C1BE /* JoystickView.swift */; }; + E6B550A12BA15E9C00DC7396 /* OverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6B550A02BA15E9C00DC7396 /* OverlayView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -57,6 +57,19 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + E6B1DC862BA32B6700473563 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 142D9F8E2BA15FBB005FE9E0 /* .swiftlint.yml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; 4E630EF22B9F7E070008F887 /* star-dash.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "star-dash.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -72,13 +85,6 @@ 4E630F122B9F7E090008F887 /* star-dashUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "star-dashUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 4E630F162B9F7E090008F887 /* star_dashUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = star_dashUITests.swift; sourceTree = ""; }; 4E630F182B9F7E090008F887 /* star_dashUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = star_dashUITestsLaunchTests.swift; sourceTree = ""; }; - E6A745112BA057040080C1BE /* MTKRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MTKRenderer.swift; sourceTree = ""; }; - E6A745122BA057040080C1BE /* ControlView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlView.swift; sourceTree = ""; }; - E6A745132BA057040080C1BE /* Renderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Renderer.swift; sourceTree = ""; }; - E6A745152BA057040080C1BE /* GameScene.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameScene.swift; sourceTree = ""; }; - E6A7451A2BA0C1890080C1BE /* PlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = ""; }; - E6A7451C2BA0CAD90080C1BE /* JoystickView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JoystickView.swift; sourceTree = ""; }; - E6B550A02BA15E9C00DC7396 /* OverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlayView.swift; sourceTree = ""; }; 4E630F262B9F7E770008F887 /* Entity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Entity.swift; sourceTree = ""; }; 4E630F292B9F7EF60008F887 /* PositionComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionComponent.swift; sourceTree = ""; }; 4E630F2B2B9F7F460008F887 /* Component.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Component.swift; sourceTree = ""; }; @@ -93,6 +99,14 @@ 4E8660612BA0964A0035530D /* Obstacle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Obstacle.swift; sourceTree = ""; }; 4E8660632BA096600035530D /* Tool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tool.swift; sourceTree = ""; }; 4E8660652BA097D40035530D /* PhysicsConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhysicsConstants.swift; sourceTree = ""; }; + E6A745112BA057040080C1BE /* MTKRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MTKRenderer.swift; sourceTree = ""; }; + E6A745122BA057040080C1BE /* ControlView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlView.swift; sourceTree = ""; }; + E6A745132BA057040080C1BE /* Renderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Renderer.swift; sourceTree = ""; }; + E6A745152BA057040080C1BE /* GameScene.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameScene.swift; sourceTree = ""; }; + E6A7451A2BA0C1890080C1BE /* PlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = ""; }; + E6A7451C2BA0CAD90080C1BE /* JoystickView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JoystickView.swift; sourceTree = ""; }; + E6B1DC8D2BA32BC700473563 /* SDPhysicsEngine */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = SDPhysicsEngine; path = ../SDPhysicsEngine; sourceTree = ""; }; + E6B550A02BA15E9C00DC7396 /* OverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlayView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -123,6 +137,7 @@ 4E630EE92B9F7E070008F887 = { isa = PBXGroup; children = ( + E6B1DC8D2BA32BC700473563 /* SDPhysicsEngine */, 4E630EF42B9F7E070008F887 /* star-dash */, 4E630F0B2B9F7E090008F887 /* star-dashTests */, 4E630F152B9F7E090008F887 /* star-dashUITests */, @@ -177,35 +192,6 @@ path = "star-dashUITests"; sourceTree = ""; }; - E6A745102BA057040080C1BE /* Rendering */ = { - isa = PBXGroup; - children = ( - E6B5509F2BA15D2000DC7396 /* MTKRenderer */, - E6A745132BA057040080C1BE /* Renderer.swift */, - ); - path = Rendering; - sourceTree = ""; - }; - E6A745142BA057040080C1BE /* GameScene */ = { - isa = PBXGroup; - children = ( - E6A745152BA057040080C1BE /* GameScene.swift */, - ); - path = GameScene; - sourceTree = ""; - }; - E6B5509F2BA15D2000DC7396 /* MTKRenderer */ = { - isa = PBXGroup; - children = ( - E6A745112BA057040080C1BE /* MTKRenderer.swift */, - E6A7451A2BA0C1890080C1BE /* PlayerView.swift */, - E6A7451C2BA0CAD90080C1BE /* JoystickView.swift */, - E6A745122BA057040080C1BE /* ControlView.swift */, - E6B550A02BA15E9C00DC7396 /* OverlayView.swift */, - ); - path = MTKRenderer; - sourceTree = ""; - }; 4E630F252B9F7E500008F887 /* Entities */ = { isa = PBXGroup; children = ( @@ -257,6 +243,35 @@ path = GameEntities; sourceTree = ""; }; + E6A745102BA057040080C1BE /* Rendering */ = { + isa = PBXGroup; + children = ( + E6B5509F2BA15D2000DC7396 /* MTKRenderer */, + E6A745132BA057040080C1BE /* Renderer.swift */, + ); + path = Rendering; + sourceTree = ""; + }; + E6A745142BA057040080C1BE /* GameScene */ = { + isa = PBXGroup; + children = ( + E6A745152BA057040080C1BE /* GameScene.swift */, + ); + path = GameScene; + sourceTree = ""; + }; + E6B5509F2BA15D2000DC7396 /* MTKRenderer */ = { + isa = PBXGroup; + children = ( + E6A745112BA057040080C1BE /* MTKRenderer.swift */, + E6A7451A2BA0C1890080C1BE /* PlayerView.swift */, + E6A7451C2BA0CAD90080C1BE /* JoystickView.swift */, + E6A745122BA057040080C1BE /* ControlView.swift */, + E6B550A02BA15E9C00DC7396 /* OverlayView.swift */, + ); + path = MTKRenderer; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -268,6 +283,7 @@ 4E630EEF2B9F7E070008F887 /* Frameworks */, 4E630EF02B9F7E070008F887 /* Resources */, 142D9F8D2BA15F91005FE9E0 /* SwiftLint */, + E6B1DC862BA32B6700473563 /* Embed Frameworks */, ); buildRules = ( ); From 72e045c9348aadddde90a69cac3dadf34eadf42f Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Thu, 14 Mar 2024 22:42:49 +0800 Subject: [PATCH 02/21] chore: Transition to use SDGameEngine --- .../SDPhysicsEngine/GameObject/GameBody.swift | 33 +++++++++ .../GameObject/GameObject.swift | 38 +++++++++++ .../GameObject/GameSpriteObject.swift | 8 +++ .../Sources/SDPhysicsEngine/GameScene.swift | 8 +++ .../SDPhysicsEngine/SDPhysicsEngine.swift | 2 - .../Sources/SDPhysicsEngine/SDScene.swift | 4 ++ star-dash/star-dash/GameScene/GameScene.swift | 68 ------------------- star-dash/star-dash/ViewController.swift | 33 ++++++++- 8 files changed, 122 insertions(+), 72 deletions(-) create mode 100644 SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameBody.swift create mode 100644 SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameObject.swift create mode 100644 SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameSpriteObject.swift create mode 100644 SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift delete mode 100644 SDPhysicsEngine/Sources/SDPhysicsEngine/SDPhysicsEngine.swift create mode 100644 SDPhysicsEngine/Sources/SDPhysicsEngine/SDScene.swift delete mode 100644 star-dash/star-dash/GameScene/GameScene.swift diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameBody.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameBody.swift new file mode 100644 index 00000000..4336189c --- /dev/null +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameBody.swift @@ -0,0 +1,33 @@ +import SpriteKit + +public class GameBody { + let body: PhysicsBodyCore + + public init(rectangleOf size: CGSize, center: CGPoint) { + body = PhysicsBodyCore(rectangleOf: size, center: center) + } + + public init(circleOf radius: CGFloat, center: CGPoint) { + body = PhysicsBodyCore(circleOfRadius: radius, center: center) + } + + public var mass: CGFloat { + get { body.mass } + set { body.mass = newValue } + } + + public var velocity: CGVector { + get { body.velocity } + set { body.velocity = newValue } + } + + 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/GameObject/GameObject.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameObject.swift new file mode 100644 index 00000000..4c099538 --- /dev/null +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameObject.swift @@ -0,0 +1,38 @@ +import SpriteKit + +public class GameObject { + let node: SKNode + + public init() { + node = SKNode() + } + + init(node: SKNode) { + self.node = node + } + + public var position: CGPoint { + get { node.position } + set { node.position = newValue } + } + + public var size: CGSize { + get { node.size } + set { node.size = newValue } + } + + public var zPosition: CGFloat { + get { node.zPosition } + set { node.zPosition = newValue } + } + + public var position: CGFloat { + get { node.zPosition } + set { node.zPosition = newValue } + } + + public var physicsBody: GameBody { + get { node.physicsBody } + set { node.physicsBody = newValue.body } + } +} diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameSpriteObject.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameSpriteObject.swift new file mode 100644 index 00000000..f08c8080 --- /dev/null +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameSpriteObject.swift @@ -0,0 +1,8 @@ +import SpriteKit + +class GameSpriteObject: Node { + + public init(imageNamed: String) { + super.init(SKSpriteNode(imageNamed: imageNamed)) + } +} \ No newline at end of file diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift new file mode 100644 index 00000000..6492f1c8 --- /dev/null +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift @@ -0,0 +1,8 @@ +import Spritekit + +class GameScene: SKScene { + + func addGameObject(_ gameObject: GameObject) { + addChild(gameObject.node) + } +} diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/SDPhysicsEngine.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/SDPhysicsEngine.swift deleted file mode 100644 index 08b22b80..00000000 --- a/SDPhysicsEngine/Sources/SDPhysicsEngine/SDPhysicsEngine.swift +++ /dev/null @@ -1,2 +0,0 @@ -// The Swift Programming Language -// https://docs.swift.org/swift-book diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/SDScene.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/SDScene.swift new file mode 100644 index 00000000..412ca5c2 --- /dev/null +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/SDScene.swift @@ -0,0 +1,4 @@ +protocol SDScene { + + func addGameObject(_ gameObject: GameObject) +} 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.. Date: Thu, 14 Mar 2024 23:36:41 +0800 Subject: [PATCH 03/21] chore: Refactor SDPhysicsEngine --- .../GameObject/GameSpriteObject.swift | 8 ----- .../Sources/SDPhysicsEngine/GameScene.swift | 8 ++--- .../SDObject.swift} | 17 ++------- .../SDPhysicsBody.swift} | 12 +++---- .../Object/SDSpriteObject.swift | 15 ++++++++ .../Sources/SDPhysicsEngine/SDScene.swift | 8 +++-- star-dash/.DS_Store | Bin 6148 -> 6148 bytes star-dash/star-dash.xcodeproj/project.pbxproj | 33 ++++++++++++++---- .../UserInterfaceState.xcuserstate | Bin 42606 -> 75081 bytes .../Rendering/MTKRenderer/MTKRenderer.swift | 1 + star-dash/star-dash/ViewController.swift | 27 +++++++------- 11 files changed, 76 insertions(+), 53 deletions(-) delete mode 100644 SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameSpriteObject.swift rename SDPhysicsEngine/Sources/SDPhysicsEngine/{GameObject/GameObject.swift => Object/SDObject.swift} (51%) rename SDPhysicsEngine/Sources/SDPhysicsEngine/{GameObject/GameBody.swift => Object/SDPhysicsBody.swift} (61%) create mode 100644 SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDSpriteObject.swift diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameSpriteObject.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameSpriteObject.swift deleted file mode 100644 index f08c8080..00000000 --- a/SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameSpriteObject.swift +++ /dev/null @@ -1,8 +0,0 @@ -import SpriteKit - -class GameSpriteObject: Node { - - public init(imageNamed: String) { - super.init(SKSpriteNode(imageNamed: imageNamed)) - } -} \ No newline at end of file diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift index 6492f1c8..766a24b1 100644 --- a/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift @@ -1,8 +1,8 @@ -import Spritekit +import SpriteKit -class GameScene: SKScene { +public class GameScene: SKScene, SDScene { - func addGameObject(_ gameObject: GameObject) { - addChild(gameObject.node) + public func addObject(_ object: SDObject) { + addChild(object.node) } } diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameObject.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDObject.swift similarity index 51% rename from SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameObject.swift rename to SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDObject.swift index 4c099538..18970f3d 100644 --- a/SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameObject.swift +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDObject.swift @@ -1,6 +1,6 @@ import SpriteKit -public class GameObject { +public class SDObject { let node: SKNode public init() { @@ -16,23 +16,12 @@ public class GameObject { set { node.position = newValue } } - public var size: CGSize { - get { node.size } - set { node.size = newValue } - } - public var zPosition: CGFloat { get { node.zPosition } set { node.zPosition = newValue } } - public var position: CGFloat { - get { node.zPosition } - set { node.zPosition = newValue } - } - - public var physicsBody: GameBody { - get { node.physicsBody } - set { node.physicsBody = newValue.body } + public var physicsBody: SDPhysicsBody? { + willSet { node.physicsBody = newValue?.body } } } diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameBody.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDPhysicsBody.swift similarity index 61% rename from SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameBody.swift rename to SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDPhysicsBody.swift index 4336189c..b196d79c 100644 --- a/SDPhysicsEngine/Sources/SDPhysicsEngine/GameObject/GameBody.swift +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDPhysicsBody.swift @@ -1,14 +1,14 @@ import SpriteKit -public class GameBody { - let body: PhysicsBodyCore +public class SDPhysicsBody { + let body: SKPhysicsBody - public init(rectangleOf size: CGSize, center: CGPoint) { - body = PhysicsBodyCore(rectangleOf: size, center: center) + public init(rectangleOf size: CGSize) { + body = SKPhysicsBody(rectangleOf: size) } - public init(circleOf radius: CGFloat, center: CGPoint) { - body = PhysicsBodyCore(circleOfRadius: radius, center: center) + public init(circleOf radius: CGFloat) { + body = SKPhysicsBody(circleOfRadius: radius) } public var mass: CGFloat { diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDSpriteObject.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDSpriteObject.swift new file mode 100644 index 00000000..8c26f157 --- /dev/null +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDSpriteObject.swift @@ -0,0 +1,15 @@ +import SpriteKit + +public class SDSpriteObject: SDObject { + let spriteNode: SKSpriteNode + + public init(imageNamed: String) { + self.spriteNode = SKSpriteNode(imageNamed: imageNamed) + super.init(node: spriteNode) + } + + public var size: CGSize { + get { spriteNode.size } + set { spriteNode.size = newValue } + } +} diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/SDScene.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/SDScene.swift index 412ca5c2..ccb5a73e 100644 --- a/SDPhysicsEngine/Sources/SDPhysicsEngine/SDScene.swift +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/SDScene.swift @@ -1,4 +1,8 @@ -protocol SDScene { +import CoreGraphics - func addGameObject(_ gameObject: GameObject) +public protocol SDScene { + + var size: CGSize { get } + + func addObject(_ object: SDObject) } diff --git a/star-dash/.DS_Store b/star-dash/.DS_Store index 447b662d095abcb474c3796f284f3edefe6c348c..6b4b512e4c460c8115c8436f10a77a17eebb02a3 100644 GIT binary patch delta 60 zcmZoMXffE}%*f2R`~GAeMy<&Oj67`JZgKicFHSzds55yTGS`xkX)_}e8{5PN_RZ`Z GfB6Ap)f0~Z delta 60 zcmZoMXffE}%*f2JFJv+gqt@gCMjkeqAA+IU7bYKI)S0{vnQO_&xS5fOjcsBB`(}2I Gzx)6*trAB7 diff --git a/star-dash/star-dash.xcodeproj/project.pbxproj b/star-dash/star-dash.xcodeproj/project.pbxproj index 05679b31..4c4c2d84 100644 --- a/star-dash/star-dash.xcodeproj/project.pbxproj +++ b/star-dash/star-dash.xcodeproj/project.pbxproj @@ -34,9 +34,10 @@ E6A745162BA057040080C1BE /* MTKRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A745112BA057040080C1BE /* MTKRenderer.swift */; }; E6A745172BA057040080C1BE /* ControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A745122BA057040080C1BE /* ControlView.swift */; }; E6A745182BA057040080C1BE /* Renderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A745132BA057040080C1BE /* Renderer.swift */; }; - E6A745192BA057040080C1BE /* GameScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A745152BA057040080C1BE /* GameScene.swift */; }; E6A7451B2BA0C1890080C1BE /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A7451A2BA0C1890080C1BE /* PlayerView.swift */; }; E6A7451D2BA0CAD90080C1BE /* JoystickView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A7451C2BA0CAD90080C1BE /* JoystickView.swift */; }; + E6B1DC902BA34A4800473563 /* SDPhysicsEngine in Sources */ = {isa = PBXBuildFile; fileRef = E6B1DC8D2BA32BC700473563 /* SDPhysicsEngine */; }; + E6B1DC932BA34A5E00473563 /* SDPhysicsEngine in Frameworks */ = {isa = PBXBuildFile; productRef = E6B1DC922BA34A5E00473563 /* SDPhysicsEngine */; }; E6B550A12BA15E9C00DC7396 /* OverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6B550A02BA15E9C00DC7396 /* OverlayView.swift */; }; /* End PBXBuildFile section */ @@ -102,7 +103,6 @@ E6A745112BA057040080C1BE /* MTKRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MTKRenderer.swift; sourceTree = ""; }; E6A745122BA057040080C1BE /* ControlView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlView.swift; sourceTree = ""; }; E6A745132BA057040080C1BE /* Renderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Renderer.swift; sourceTree = ""; }; - E6A745152BA057040080C1BE /* GameScene.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameScene.swift; sourceTree = ""; }; E6A7451A2BA0C1890080C1BE /* PlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = ""; }; E6A7451C2BA0CAD90080C1BE /* JoystickView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JoystickView.swift; sourceTree = ""; }; E6B1DC8D2BA32BC700473563 /* SDPhysicsEngine */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = SDPhysicsEngine; path = ../SDPhysicsEngine; sourceTree = ""; }; @@ -114,6 +114,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E6B1DC932BA34A5E00473563 /* SDPhysicsEngine in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -143,6 +144,7 @@ 4E630F152B9F7E090008F887 /* star-dashUITests */, 142D9F8E2BA15FBB005FE9E0 /* .swiftlint.yml */, 4E630EF32B9F7E070008F887 /* Products */, + E6B1DC912BA34A5E00473563 /* Frameworks */, ); sourceTree = ""; }; @@ -159,7 +161,6 @@ 4E630EF42B9F7E070008F887 /* star-dash */ = { isa = PBXGroup; children = ( - E6A745142BA057040080C1BE /* GameScene */, E6A745102BA057040080C1BE /* Rendering */, 4E86605D2BA095CC0035530D /* Constants */, 4E630F352B9F91C20008F887 /* Enums */, @@ -252,12 +253,11 @@ path = Rendering; sourceTree = ""; }; - E6A745142BA057040080C1BE /* GameScene */ = { + E6B1DC912BA34A5E00473563 /* Frameworks */ = { isa = PBXGroup; children = ( - E6A745152BA057040080C1BE /* GameScene.swift */, ); - path = GameScene; + name = Frameworks; sourceTree = ""; }; E6B5509F2BA15D2000DC7396 /* MTKRenderer */ = { @@ -288,8 +288,12 @@ buildRules = ( ); dependencies = ( + E6B1DC8F2BA3459600473563 /* PBXTargetDependency */, ); name = "star-dash"; + packageProductDependencies = ( + E6B1DC922BA34A5E00473563 /* SDPhysicsEngine */, + ); productName = "star-dash"; productReference = 4E630EF22B9F7E070008F887 /* star-dash.app */; productType = "com.apple.product-type.application"; @@ -427,6 +431,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + E6B1DC902BA34A4800473563 /* SDPhysicsEngine in Sources */, 4E630F272B9F7E770008F887 /* Entity.swift in Sources */, 4E630F2C2B9F7F460008F887 /* Component.swift in Sources */, 4E8660622BA0964A0035530D /* Obstacle.swift in Sources */, @@ -443,7 +448,6 @@ E6A7451B2BA0C1890080C1BE /* PlayerView.swift in Sources */, E6A745162BA057040080C1BE /* MTKRenderer.swift in Sources */, E6A745182BA057040080C1BE /* Renderer.swift in Sources */, - E6A745192BA057040080C1BE /* GameScene.swift in Sources */, E6A745172BA057040080C1BE /* ControlView.swift in Sources */, E6A7451D2BA0CAD90080C1BE /* JoystickView.swift in Sources */, E6B550A12BA15E9C00DC7396 /* OverlayView.swift in Sources */, @@ -484,6 +488,10 @@ target = 4E630EF12B9F7E070008F887 /* star-dash */; targetProxy = 4E630F132B9F7E090008F887 /* PBXContainerItemProxy */; }; + E6B1DC8F2BA3459600473563 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = E6B1DC8E2BA3459600473563 /* SDPhysicsEngine */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -809,6 +817,17 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCSwiftPackageProductDependency section */ + E6B1DC8E2BA3459600473563 /* SDPhysicsEngine */ = { + isa = XCSwiftPackageProductDependency; + productName = SDPhysicsEngine; + }; + E6B1DC922BA34A5E00473563 /* SDPhysicsEngine */ = { + isa = XCSwiftPackageProductDependency; + productName = SDPhysicsEngine; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 4E630EEA2B9F7E070008F887 /* Project object */; } diff --git a/star-dash/star-dash.xcodeproj/project.xcworkspace/xcuserdata/proglab.xcuserdatad/UserInterfaceState.xcuserstate b/star-dash/star-dash.xcodeproj/project.xcworkspace/xcuserdata/proglab.xcuserdatad/UserInterfaceState.xcuserstate index b942bb0a5cc8fc711460f8bc73ca8cff86d7ec4d..b7862236c6bf0adc4965fb4ee1ba51f736a9bf24 100644 GIT binary patch literal 75081 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 C&Q8bx literal 42606 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 Date: Fri, 15 Mar 2024 01:41:21 +0800 Subject: [PATCH 04/21] chore: Add Scene delegate --- .../Sources/SDPhysicsEngine/GameScene.swift | 16 ++++++++++++++++ .../SDPhysicsEngine/SDSceneDelegate.swift | 4 ++++ star-dash/star-dash/ViewController.swift | 9 +++++++++ 3 files changed, 29 insertions(+) create mode 100644 SDPhysicsEngine/Sources/SDPhysicsEngine/SDSceneDelegate.swift diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift index 766a24b1..eac7c15a 100644 --- a/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift @@ -2,6 +2,22 @@ import SpriteKit public class GameScene: SKScene, SDScene { + var sceneDelegate: SDSceneDelegate? + + private var lastUpdateTime: TimeInterval? + + override func update(_ currentTime: TimeInterval) { + guard let lastUpdateTime = lastUpdateTime else { + lastUpdateTime = currentTime + return + } + + let deltaTime = currentTime - lastUpdateTime + lastUpdateTime = currentTime + + sceneDelegate?.update(self, deltaTime: deltaTime) + } + public func addObject(_ object: SDObject) { addChild(object.node) } diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/SDSceneDelegate.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/SDSceneDelegate.swift new file mode 100644 index 00000000..4f704221 --- /dev/null +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/SDSceneDelegate.swift @@ -0,0 +1,4 @@ +protocol SDSceneDelegate: AnyObject { + + func update(_ SDScene: scene, deltaTime: TimeInterval) +} \ No newline at end of file diff --git a/star-dash/star-dash/ViewController.swift b/star-dash/star-dash/ViewController.swift index 91217197..11d884a6 100644 --- a/star-dash/star-dash/ViewController.swift +++ b/star-dash/star-dash/ViewController.swift @@ -61,3 +61,12 @@ class ViewController: UIViewController { scene.addObject(platform) } } + +extension ViewController: SDSceneDelegate { + + func update(_ SDScene: scene, deltaTime: TimeInterval) { + // TODO: Sync SDObjects into Entities + // TODO: Update Game Logic + // TODO: Sync Entities into SDObjects + } +} From 8a0a8c391633abd1f304c6348fc32ca22d318232 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Fri, 15 Mar 2024 01:43:56 +0800 Subject: [PATCH 05/21] fix: Fix syntax issues --- .../Sources/SDPhysicsEngine/GameScene.swift | 4 ++-- .../SDPhysicsEngine/SDSceneDelegate.swift | 6 +++--- star-dash/.DS_Store | Bin 6148 -> 6148 bytes .../UserInterfaceState.xcuserstate | Bin 75081 -> 77480 bytes star-dash/star-dash/ViewController.swift | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift index eac7c15a..5056e098 100644 --- a/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift @@ -6,14 +6,14 @@ public class GameScene: SKScene, SDScene { private var lastUpdateTime: TimeInterval? - override func update(_ currentTime: TimeInterval) { + override public func update(_ currentTime: TimeInterval) { guard let lastUpdateTime = lastUpdateTime else { lastUpdateTime = currentTime return } let deltaTime = currentTime - lastUpdateTime - lastUpdateTime = currentTime + self.lastUpdateTime = currentTime sceneDelegate?.update(self, deltaTime: deltaTime) } diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/SDSceneDelegate.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/SDSceneDelegate.swift index 4f704221..76b6d930 100644 --- a/SDPhysicsEngine/Sources/SDPhysicsEngine/SDSceneDelegate.swift +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/SDSceneDelegate.swift @@ -1,4 +1,4 @@ -protocol SDSceneDelegate: AnyObject { +public protocol SDSceneDelegate: AnyObject { - func update(_ SDScene: scene, deltaTime: TimeInterval) -} \ No newline at end of file + func update(_ scene: SDScene, deltaTime: Double) +} diff --git a/star-dash/.DS_Store b/star-dash/.DS_Store index 6b4b512e4c460c8115c8436f10a77a17eebb02a3..3475bf2c9b7eabc284b63343836694e9e711bf89 100644 GIT binary patch delta 18 ZcmZoMXffE}!pL}NGB>04<^skkVgNa11-SqK delta 19 acmZoMXffE}!pOAy{$w6Tt<43DQ^Wv7{RWZ% diff --git a/star-dash/star-dash.xcodeproj/project.xcworkspace/xcuserdata/proglab.xcuserdatad/UserInterfaceState.xcuserstate b/star-dash/star-dash.xcodeproj/project.xcworkspace/xcuserdata/proglab.xcuserdatad/UserInterfaceState.xcuserstate index b7862236c6bf0adc4965fb4ee1ba51f736a9bf24..4e4d81e7cf3f6943e514b00dd7ab97349720dc2c 100644 GIT binary patch literal 77480 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�TMn@gevyJQp8@kH*K~WATaj416BG7%#wo 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 Date: Fri, 15 Mar 2024 02:04:40 +0800 Subject: [PATCH 06/21] chore: Remove dependency to SDPhysicsEngine from renderer --- .../star-dash/Rendering/MTKRenderer/MTKRenderer.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/star-dash/star-dash/Rendering/MTKRenderer/MTKRenderer.swift b/star-dash/star-dash/Rendering/MTKRenderer/MTKRenderer.swift index d0ade139..37041a6c 100644 --- a/star-dash/star-dash/Rendering/MTKRenderer/MTKRenderer.swift +++ b/star-dash/star-dash/Rendering/MTKRenderer/MTKRenderer.swift @@ -1,17 +1,16 @@ import UIKit import SpriteKit import MetalKit -import SDPhysicsEngine /** `MTKRenderer` is a `Renderer` that uses MetalKit and SpriteKit to render the game on to the iOS device. - The `GameScene` is rendered through a MetalKit while the controls + The `SKScene` is rendered through a MetalKit while the controls and game information overlay is rendered throuhg UIKit. */ class MTKRenderer: NSObject, Renderer { - var scene: GameScene + var scene: SKScene var device: MTLDevice var commandQueue: MTLCommandQueue @@ -19,7 +18,7 @@ class MTKRenderer: NSObject, Renderer { var playerView: PlayerView? - init?(scene: GameScene) { + init?(scene: SKScene) { self.scene = scene guard let device = MTLCreateSystemDefaultDevice(), From 0acbcd21f9798d87f03c24c04f3d018b4c491af1 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sat, 16 Mar 2024 00:59:51 +0800 Subject: [PATCH 07/21] wip: Synchronise from entities to game scene --- .../Object/SDSpriteObject.swift | 20 ++++++ .../Rendering/RenderSynchronizer.swift | 70 +++++++++++++++++++ .../Rendering/SyncModule/CreationModule.swift | 3 + .../Rendering/SyncModule/ObjectModule.swift | 30 ++++++++ .../Rendering/SyncModule/PhysicsModule.swift | 37 ++++++++++ .../Rendering/SyncModule/SpriteModule.swift | 42 +++++++++++ .../Rendering/SyncModule/SyncModule.swift | 7 ++ star-dash/star-dash/ViewController.swift | 6 ++ 8 files changed, 215 insertions(+) create mode 100644 star-dash/star-dash/Rendering/RenderSynchronizer.swift create mode 100644 star-dash/star-dash/Rendering/SyncModule/CreationModule.swift create mode 100644 star-dash/star-dash/Rendering/SyncModule/ObjectModule.swift create mode 100644 star-dash/star-dash/Rendering/SyncModule/PhysicsModule.swift create mode 100644 star-dash/star-dash/Rendering/SyncModule/SpriteModule.swift create mode 100644 star-dash/star-dash/Rendering/SyncModule/SyncModule.swift diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDSpriteObject.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDSpriteObject.swift index 8c26f157..dd7a2b23 100644 --- a/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDSpriteObject.swift +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDSpriteObject.swift @@ -2,6 +2,8 @@ import SpriteKit public class SDSpriteObject: SDObject { let spriteNode: SKSpriteNode + + var activeTexture: String? public init(imageNamed: String) { self.spriteNode = SKSpriteNode(imageNamed: imageNamed) @@ -12,4 +14,22 @@ public class SDSpriteObject: SDObject { get { spriteNode.size } set { spriteNode.size = newValue } } + + public var 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 var loadTexture(named: String) -> [SKTexture] { + let textureAtlas = SKTextureAtlas(named: named) + var frames = [SKTexture]() + for idx in 0.. SDObject? +} diff --git a/star-dash/star-dash/Rendering/SyncModule/ObjectModule.swift b/star-dash/star-dash/Rendering/SyncModule/ObjectModule.swift new file mode 100644 index 00000000..53e9ae05 --- /dev/null +++ b/star-dash/star-dash/Rendering/SyncModule/ObjectModule.swift @@ -0,0 +1,30 @@ +import SDPhysicsEngine + +protocol ObjectModule { + let entitiesManager: EntitiesManager + + init(entitiesManager: EntitiesManager) { + self.entitiesManager = entitiesManager + } + + func sync(entity: Entity, into object: SDObject) { + guard let positionComponent = entityManager.component(ofType: PositionComponent.self, of: entity) else { + return + } + + object.position = positionComponent.position + object.rotation = positionComponent.rotation + } + + func create(for: object: SDObject, from entity: Entity) { + guard let positionComponent = entityManager.component(ofType: PositionComponent.self, of: entity) else { + return + } + + let object = SDObject() + object.position = positionComponent.position + object.rotation = positionComponent.rotation + + return object + } +} diff --git a/star-dash/star-dash/Rendering/SyncModule/PhysicsModule.swift b/star-dash/star-dash/Rendering/SyncModule/PhysicsModule.swift new file mode 100644 index 00000000..2deab07d --- /dev/null +++ b/star-dash/star-dash/Rendering/SyncModule/PhysicsModule.swift @@ -0,0 +1,37 @@ +import SDPhysicsEngine + +protocol ObjectModule { + let entitiesManager: EntitiesManager + + init(entitiesManager: EntitiesManager) { + self.entitiesManager = entitiesManager + } + + func sync(entity: Entity, into object: SDObject) { + guard let physicsComponent = entityManager.component(ofType: PhysicsComponent.self, of: entity), + 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) else { + return + } + + object.physicsBody = createRectanglePhysicsBody(physicsComponent) + } + + private func createRectanglePhysicsBody(physicsComponent: PhysicsComponent) { + guard let size = physicsComponent.size else { + fatalError("Rectangle PhysicsComponent does not have a size") + } + + return PhysicsBody(rectangleOf: CGSize(width: 50, height: 50)) + } +} diff --git a/star-dash/star-dash/Rendering/SyncModule/SpriteModule.swift b/star-dash/star-dash/Rendering/SyncModule/SpriteModule.swift new file mode 100644 index 00000000..a7c61750 --- /dev/null +++ b/star-dash/star-dash/Rendering/SyncModule/SpriteModule.swift @@ -0,0 +1,42 @@ +import SDPhysicsEngine + +class SpriteModule { + let entitiesManager: EntitiesManager + + init(entitiesManager: EntitiesManager) { + self.entitiesManager = entitiesManager + } + + func sync(entity: Entity, into object: SDObject) { + guard let spriteComponent = entityManager.component(ofType: SpriteComponent.self, of: entity), + let spriteObject = object as? SDSpriteObject else { + return + } + + if spriteComponent.image != spriteObject.activeTexture { + spriteObject.runTexture(spriteComponent.image) + } + } + + func create(for: object: SDObject, from entity: Entity) { + return + } +} + +extension SpriteModule: CreationModule { + func createObject(from entity: Entity) -> SDObject? { + var newObject = SDObject() + if let spriteComponent = entityManager.component(ofType: SpriteComponent.self, of: entity) { + newObject = SDSpriteObject() + } + + guard let positionComponent = entityManager.component(ofType: SpriteComponent.self, of: entity) else { + return nil + } + + newObject.position = positionComponent.position + newObject.rotation = positionComponent.rotation + + return newObject + } +} diff --git a/star-dash/star-dash/Rendering/SyncModule/SyncModule.swift b/star-dash/star-dash/Rendering/SyncModule/SyncModule.swift new file mode 100644 index 00000000..a6ff5f62 --- /dev/null +++ b/star-dash/star-dash/Rendering/SyncModule/SyncModule.swift @@ -0,0 +1,7 @@ +import SDPhysicsEngine + +protocol SyncModule { + + func sync(entity: Entity, into object: SDObject) + func create(for: object: SDObject, from entity: Entity) +} \ No newline at end of file diff --git a/star-dash/star-dash/ViewController.swift b/star-dash/star-dash/ViewController.swift index 11d884a6..33d950b8 100644 --- a/star-dash/star-dash/ViewController.swift +++ b/star-dash/star-dash/ViewController.swift @@ -12,6 +12,8 @@ class ViewController: UIViewController { var scene: SDScene? var renderer: Renderer? + var renderSynchronizer: RenderSynchronizer? + var gameEngine: GameEngine? override func viewDidLoad() { super.viewDidLoad() @@ -19,6 +21,10 @@ class ViewController: UIViewController { let scene = GameScene(size: CGSize(width: 4_842, height: 1_040)) scene.scaleMode = .aspectFill self.scene = scene + let gameEngine = GameEngine(scene: scene) + self.gameEngine = gameEngine + self.renderSynchronizer = RenderSynchronizer(entitiesManager: gameEngine.entitiesManager) + setupGame() guard let renderer = MTKRenderer(scene: scene) else { From d9a1dac9b7f523afd111d0484a48ac154933e7c0 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sat, 16 Mar 2024 01:50:20 +0800 Subject: [PATCH 08/21] fix: Fix broken pbxproj file --- star-dash/star-dash.xcodeproj/project.pbxproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/star-dash/star-dash.xcodeproj/project.pbxproj b/star-dash/star-dash.xcodeproj/project.pbxproj index c7e05e1f..d23e1b0a 100644 --- a/star-dash/star-dash.xcodeproj/project.pbxproj +++ b/star-dash/star-dash.xcodeproj/project.pbxproj @@ -922,6 +922,9 @@ isa = XCSwiftPackageProductDependency; package = 14E247912BA2CA920071FFC0 /* XCRemoteSwiftPackageReference "swift-collections" */; productName = DequeModule; + }; +/* End XCSwiftPackageProductDependency section */ + }; rootObject = 4E630EEA2B9F7E070008F887 /* Project object */; } From 213d101c0f7880d26961d1d79c14a71a1470a5d8 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sat, 16 Mar 2024 01:58:13 +0800 Subject: [PATCH 09/21] fix: Fix syntax issues --- .../star-dash/Rendering/RenderSynchronizer.swift | 14 +++++++------- .../Rendering/SyncModule/ObjectModule.swift | 6 +++--- .../Rendering/SyncModule/PhysicsModule.swift | 6 +++--- .../Rendering/SyncModule/SpriteModule.swift | 6 +++--- star-dash/star-dash/ViewController.swift | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/star-dash/star-dash/Rendering/RenderSynchronizer.swift b/star-dash/star-dash/Rendering/RenderSynchronizer.swift index 766deb12..00e22364 100644 --- a/star-dash/star-dash/Rendering/RenderSynchronizer.swift +++ b/star-dash/star-dash/Rendering/RenderSynchronizer.swift @@ -2,14 +2,14 @@ import SDPhysicsEngine class RenderSyncronizer() { - var entitiesManager: EntitiesManager + var entityManager: EntityManager var entitiesMap: [Entity: SDObject] var modules: [SyncModule] var creationModule: CreationModule? - init(entitiesManager: EntitiesManager) { - self.entitiesManager = entitiesManager + init(entityManager: EntityManager) { + self.entityManager = entityManager modules = [] entitiesMap = [:] @@ -19,7 +19,7 @@ class RenderSyncronizer() { func sync() { var toRemove = Set(entitiesMap.keys) - for entity in entitiesManager.entitiesMap.values { + for entity in entityManager.entitiesMap.values { toRemove.remove(entity) if let object = entitiesMap[entity] { update(object: object, from: entity) @@ -38,12 +38,12 @@ class RenderSyncronizer() { } private func registerModules() { - let spriteModule = SpriteModule(entitiesManager: entitiesManager) + let spriteModule = SpriteModule(entityManager: entityManager) self.creationModule = spriteModule registerModule(spriteModule) - registerModule(ObjectModule(entitiesManager: entitiesManager)) - registerModule(PhysicsModule(entitiesManager: entitiesManager)) + registerModule(ObjectModule(entityManager: entityManager)) + registerModule(PhysicsModule(entityManager: entityManager)) } private func update(object: SDObject, from entity: Entity) { diff --git a/star-dash/star-dash/Rendering/SyncModule/ObjectModule.swift b/star-dash/star-dash/Rendering/SyncModule/ObjectModule.swift index 53e9ae05..f9eebc05 100644 --- a/star-dash/star-dash/Rendering/SyncModule/ObjectModule.swift +++ b/star-dash/star-dash/Rendering/SyncModule/ObjectModule.swift @@ -1,10 +1,10 @@ import SDPhysicsEngine protocol ObjectModule { - let entitiesManager: EntitiesManager + let entityManager: EntityManager - init(entitiesManager: EntitiesManager) { - self.entitiesManager = entitiesManager + init(entityManager: EntityManager) { + self.entityManager = entityManager } func sync(entity: Entity, into object: SDObject) { diff --git a/star-dash/star-dash/Rendering/SyncModule/PhysicsModule.swift b/star-dash/star-dash/Rendering/SyncModule/PhysicsModule.swift index 2deab07d..4f003070 100644 --- a/star-dash/star-dash/Rendering/SyncModule/PhysicsModule.swift +++ b/star-dash/star-dash/Rendering/SyncModule/PhysicsModule.swift @@ -1,10 +1,10 @@ import SDPhysicsEngine protocol ObjectModule { - let entitiesManager: EntitiesManager + let entityManager: EntityManager - init(entitiesManager: EntitiesManager) { - self.entitiesManager = entitiesManager + init(entityManager: EntityManager) { + self.entityManager = entityManager } func sync(entity: Entity, into object: SDObject) { diff --git a/star-dash/star-dash/Rendering/SyncModule/SpriteModule.swift b/star-dash/star-dash/Rendering/SyncModule/SpriteModule.swift index a7c61750..f3ad474e 100644 --- a/star-dash/star-dash/Rendering/SyncModule/SpriteModule.swift +++ b/star-dash/star-dash/Rendering/SyncModule/SpriteModule.swift @@ -1,10 +1,10 @@ import SDPhysicsEngine class SpriteModule { - let entitiesManager: EntitiesManager + let entityManager: EntityManager - init(entitiesManager: EntitiesManager) { - self.entitiesManager = entitiesManager + init(entityManager: EntityManager) { + self.entityManager = entityManager } func sync(entity: Entity, into object: SDObject) { diff --git a/star-dash/star-dash/ViewController.swift b/star-dash/star-dash/ViewController.swift index 113b79b7..4fef6134 100644 --- a/star-dash/star-dash/ViewController.swift +++ b/star-dash/star-dash/ViewController.swift @@ -23,7 +23,7 @@ class ViewController: UIViewController { self.scene = scene let gameEngine = GameEngine(scene: scene) self.gameEngine = gameEngine - self.renderSynchronizer = RenderSynchronizer(entitiesManager: gameEngine.entitiesManager) + self.renderSynchronizer = RenderSynchronizer(entityManager: gameEngine.entityManager) setupGame() From fbf5c06147737cc524cda26216718cb6b8d9830a Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sat, 16 Mar 2024 03:13:40 +0800 Subject: [PATCH 10/21] fix: Fix syntax issues --- .../SDPhysicsEngine/Object/SDObject.swift | 10 +++ .../Object/SDPhysicsBody.swift | 7 ++ .../Object/SDSpriteObject.swift | 6 +- star-dash/star-dash.xcodeproj/project.pbxproj | 69 ++++++++++++------- .../star-dash/GameEngine/GameEngine.swift | 4 +- .../Rendering/RenderSynchronizer.swift | 24 +++---- .../Rendering/SyncModule/CreationModule.swift | 2 + .../Rendering/SyncModule/ObjectModule.swift | 19 ++--- .../Rendering/SyncModule/PhysicsModule.swift | 19 +++-- .../Rendering/SyncModule/SpriteModule.swift | 17 ++--- .../Rendering/SyncModule/SyncModule.swift | 4 +- star-dash/star-dash/ViewController.swift | 2 +- 12 files changed, 104 insertions(+), 79 deletions(-) diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDObject.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDObject.swift index 18970f3d..51910710 100644 --- a/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDObject.swift +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDObject.swift @@ -2,6 +2,8 @@ import SpriteKit public class SDObject { let node: SKNode + + var innerRotation: CGFloat = 0 public init() { node = SKNode() @@ -20,6 +22,14 @@ public class SDObject { 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 index b196d79c..bdf90820 100644 --- a/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDPhysicsBody.swift +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDPhysicsBody.swift @@ -20,6 +20,13 @@ public class SDPhysicsBody { get { body.velocity } set { body.velocity = newValue } } + + public var force: CGVector { + //get { body.force } + //set { body.force = newValue} + get { CGVector(dx: 0, dy: 0) } + set { } + } public var affectedByGravity: Bool { get { body.affectedByGravity } diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDSpriteObject.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDSpriteObject.swift index dd7a2b23..60835fab 100644 --- a/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDSpriteObject.swift +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDSpriteObject.swift @@ -3,7 +3,7 @@ import SpriteKit public class SDSpriteObject: SDObject { let spriteNode: SKSpriteNode - var activeTexture: String? + public var activeTexture: String? public init(imageNamed: String) { self.spriteNode = SKSpriteNode(imageNamed: imageNamed) @@ -15,7 +15,7 @@ public class SDSpriteObject: SDObject { set { spriteNode.size = newValue } } - public var runTexture(named: String) { + 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) @@ -24,7 +24,7 @@ public class SDSpriteObject: SDObject { activeTexture = named } - private var loadTexture(named: String) -> [SKTexture] { + private func loadTexture(named: String) -> [SKTexture] { let textureAtlas = SKTextureAtlas(named: named) var frames = [SKTexture]() for idx in 0.. SDObject? } diff --git a/star-dash/star-dash/Rendering/SyncModule/ObjectModule.swift b/star-dash/star-dash/Rendering/SyncModule/ObjectModule.swift index f9eebc05..f492433a 100644 --- a/star-dash/star-dash/Rendering/SyncModule/ObjectModule.swift +++ b/star-dash/star-dash/Rendering/SyncModule/ObjectModule.swift @@ -1,6 +1,7 @@ +import CoreGraphics import SDPhysicsEngine -protocol ObjectModule { +class ObjectModule: SyncModule { let entityManager: EntityManager init(entityManager: EntityManager) { @@ -8,23 +9,15 @@ protocol ObjectModule { } func sync(entity: Entity, into object: SDObject) { - guard let positionComponent = entityManager.component(ofType: PositionComponent.self, of: entity) else { + guard let positionComponent = entityManager.component(ofType: PositionComponent.self, of: entity.id) else { return } object.position = positionComponent.position - object.rotation = positionComponent.rotation + object.rotation = CGFloat(positionComponent.rotation) } - func create(for: object: SDObject, from entity: Entity) { - guard let positionComponent = entityManager.component(ofType: PositionComponent.self, of: entity) else { - return - } - - let object = SDObject() - object.position = positionComponent.position - object.rotation = positionComponent.rotation - - return object + func create(for object: SDObject, from entity: Entity) { + return } } diff --git a/star-dash/star-dash/Rendering/SyncModule/PhysicsModule.swift b/star-dash/star-dash/Rendering/SyncModule/PhysicsModule.swift index 4f003070..390f7f39 100644 --- a/star-dash/star-dash/Rendering/SyncModule/PhysicsModule.swift +++ b/star-dash/star-dash/Rendering/SyncModule/PhysicsModule.swift @@ -1,6 +1,7 @@ +import CoreGraphics import SDPhysicsEngine -protocol ObjectModule { +class PhysicsModule: SyncModule { let entityManager: EntityManager init(entityManager: EntityManager) { @@ -8,7 +9,7 @@ protocol ObjectModule { } func sync(entity: Entity, into object: SDObject) { - guard let physicsComponent = entityManager.component(ofType: PhysicsComponent.self, of: entity), + guard let physicsComponent = entityManager.component(ofType: PhysicsComponent.self, of: entity.id), let body = object.physicsBody else { return } @@ -19,19 +20,15 @@ protocol ObjectModule { body.affectedByGravity = physicsComponent.affectedByGravity } - func create(for: object: SDObject, from entity: Entity) { - guard let physicsComponent = entityManager.component(ofType: PhysicsComponent.self, of: entity) else { + 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) + object.physicsBody = createRectanglePhysicsBody(physicsComponent: physicsComponent) } - private func createRectanglePhysicsBody(physicsComponent: PhysicsComponent) { - guard let size = physicsComponent.size else { - fatalError("Rectangle PhysicsComponent does not have a size") - } - - return PhysicsBody(rectangleOf: CGSize(width: 50, height: 50)) + private func createRectanglePhysicsBody(physicsComponent: PhysicsComponent) -> SDPhysicsBody { + return SDPhysicsBody(rectangleOf: CGSize(width: 50, height: 50)) } } diff --git a/star-dash/star-dash/Rendering/SyncModule/SpriteModule.swift b/star-dash/star-dash/Rendering/SyncModule/SpriteModule.swift index f3ad474e..70a77c69 100644 --- a/star-dash/star-dash/Rendering/SyncModule/SpriteModule.swift +++ b/star-dash/star-dash/Rendering/SyncModule/SpriteModule.swift @@ -1,6 +1,7 @@ +import CoreGraphics import SDPhysicsEngine -class SpriteModule { +class SpriteModule: SyncModule { let entityManager: EntityManager init(entityManager: EntityManager) { @@ -8,17 +9,17 @@ class SpriteModule { } func sync(entity: Entity, into object: SDObject) { - guard let spriteComponent = entityManager.component(ofType: SpriteComponent.self, of: 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(spriteComponent.image) + spriteObject.runTexture(named: spriteComponent.image) } } - func create(for: object: SDObject, from entity: Entity) { + func create(for object: SDObject, from entity: Entity) { return } } @@ -26,16 +27,16 @@ class SpriteModule { extension SpriteModule: CreationModule { func createObject(from entity: Entity) -> SDObject? { var newObject = SDObject() - if let spriteComponent = entityManager.component(ofType: SpriteComponent.self, of: entity) { - newObject = SDSpriteObject() + if let spriteComponent = entityManager.component(ofType: SpriteComponent.self, of: entity.id) { + newObject = SDSpriteObject(imageNamed: "PlayerRedNose") } - guard let positionComponent = entityManager.component(ofType: SpriteComponent.self, of: entity) else { + guard let positionComponent = entityManager.component(ofType: PositionComponent.self, of: entity.id) else { return nil } newObject.position = positionComponent.position - newObject.rotation = positionComponent.rotation + newObject.rotation = CGFloat(positionComponent.rotation) return newObject } diff --git a/star-dash/star-dash/Rendering/SyncModule/SyncModule.swift b/star-dash/star-dash/Rendering/SyncModule/SyncModule.swift index a6ff5f62..b16db1f3 100644 --- a/star-dash/star-dash/Rendering/SyncModule/SyncModule.swift +++ b/star-dash/star-dash/Rendering/SyncModule/SyncModule.swift @@ -3,5 +3,5 @@ import SDPhysicsEngine protocol SyncModule { func sync(entity: Entity, into object: SDObject) - func create(for: object: SDObject, from entity: Entity) -} \ No newline at end of file + func create(for object: SDObject, from entity: Entity) +} diff --git a/star-dash/star-dash/ViewController.swift b/star-dash/star-dash/ViewController.swift index 4fef6134..a20d8c31 100644 --- a/star-dash/star-dash/ViewController.swift +++ b/star-dash/star-dash/ViewController.swift @@ -21,7 +21,7 @@ class ViewController: UIViewController { let scene = GameScene(size: CGSize(width: 4_842, height: 1_040)) scene.scaleMode = .aspectFill self.scene = scene - let gameEngine = GameEngine(scene: scene) + let gameEngine = GameEngine() self.gameEngine = gameEngine self.renderSynchronizer = RenderSynchronizer(entityManager: gameEngine.entityManager) From f1ca1a1125b86df2d64860094193fb4e26ab6b19 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sat, 16 Mar 2024 12:05:15 +0800 Subject: [PATCH 11/21] wip: setup game entieies --- star-dash/star-dash/ViewController.swift | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/star-dash/star-dash/ViewController.swift b/star-dash/star-dash/ViewController.swift index a20d8c31..39eeb58b 100644 --- a/star-dash/star-dash/ViewController.swift +++ b/star-dash/star-dash/ViewController.swift @@ -25,7 +25,8 @@ class ViewController: UIViewController { self.gameEngine = gameEngine self.renderSynchronizer = RenderSynchronizer(entityManager: gameEngine.entityManager) - setupGame() + //setupGame() + setupGameEntities() guard let renderer = MTKRenderer(scene: scene) else { return @@ -66,6 +67,14 @@ class ViewController: UIViewController { platform.position = CGPoint(x: scene.size.width / 2, y: scene.size.height / 2 - 400) scene.addObject(platform) } + + func setupGameEntities() { + let player = Player( + position: CGPoint(x: scene.size.width / 2, y: scene.size.height / 2 + 200), + playerSprite: PlayerSprite.RedNose + ) + player.setUpAndAdd(to: gameEngine.entityManager) + } } extension ViewController: SDSceneDelegate { @@ -74,5 +83,6 @@ extension ViewController: SDSceneDelegate { // TODO: Sync SDObjects into Entities // TODO: Update Game Logic // TODO: Sync Entities into SDObjects + RenderSynchronizer.sync() } } From 98971b3ab71c116a11ed66d8201965af00a16924 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sat, 16 Mar 2024 15:53:58 +0800 Subject: [PATCH 12/21] fix: Fix syntax --- .../Sources/SDPhysicsEngine/GameScene.swift | 4 ++- .../GameEngine/Entities/EntityManager.swift | 5 +-- .../Rendering/RenderSynchronizer.swift | 11 ++++--- .../Rendering/SyncModule/ObjectModule.swift | 3 +- .../Rendering/SyncModule/PhysicsModule.swift | 2 +- .../Rendering/SyncModule/SpriteModule.swift | 13 ++++---- star-dash/star-dash/ViewController.swift | 33 +++++-------------- 7 files changed, 31 insertions(+), 40 deletions(-) diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift index 5056e098..a258c3d1 100644 --- a/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/GameScene.swift @@ -2,11 +2,13 @@ import SpriteKit public class GameScene: SKScene, SDScene { - var sceneDelegate: SDSceneDelegate? + 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 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/Rendering/RenderSynchronizer.swift b/star-dash/star-dash/Rendering/RenderSynchronizer.swift index f27293be..6652c3fc 100644 --- a/star-dash/star-dash/Rendering/RenderSynchronizer.swift +++ b/star-dash/star-dash/Rendering/RenderSynchronizer.swift @@ -3,13 +3,15 @@ import SDPhysicsEngine class RenderSynchronizer { var entityManager: EntityManager + var scene: SDScene var entitiesMap: [EntityId: SDObject] var modules: [SyncModule] var creationModule: CreationModule? - init(entityManager: EntityManager) { + init(entityManager: EntityManager, scene: SDScene) { self.entityManager = entityManager + self.scene = scene modules = [] entitiesMap = [:] @@ -18,11 +20,11 @@ class RenderSynchronizer { func sync() { var toRemove = Set(entitiesMap.keys) - + for entity in entityManager.entityMap.values { toRemove.remove(entity.id) if let object = entitiesMap[entity.id] { - update(object: object, from: entity) + //update(object: object, from: entity) } else { createObject(from: entity) } @@ -56,13 +58,14 @@ class RenderSynchronizer { 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) { - return } } diff --git a/star-dash/star-dash/Rendering/SyncModule/ObjectModule.swift b/star-dash/star-dash/Rendering/SyncModule/ObjectModule.swift index f492433a..40e35df8 100644 --- a/star-dash/star-dash/Rendering/SyncModule/ObjectModule.swift +++ b/star-dash/star-dash/Rendering/SyncModule/ObjectModule.swift @@ -7,7 +7,7 @@ class ObjectModule: SyncModule { init(entityManager: EntityManager) { self.entityManager = entityManager } - + func sync(entity: Entity, into object: SDObject) { guard let positionComponent = entityManager.component(ofType: PositionComponent.self, of: entity.id) else { return @@ -18,6 +18,5 @@ class ObjectModule: SyncModule { } func create(for object: SDObject, from entity: Entity) { - return } } diff --git a/star-dash/star-dash/Rendering/SyncModule/PhysicsModule.swift b/star-dash/star-dash/Rendering/SyncModule/PhysicsModule.swift index 390f7f39..a46ffae8 100644 --- a/star-dash/star-dash/Rendering/SyncModule/PhysicsModule.swift +++ b/star-dash/star-dash/Rendering/SyncModule/PhysicsModule.swift @@ -7,7 +7,7 @@ class PhysicsModule: SyncModule { init(entityManager: EntityManager) { self.entityManager = entityManager } - + func sync(entity: Entity, into object: SDObject) { guard let physicsComponent = entityManager.component(ofType: PhysicsComponent.self, of: entity.id), let body = object.physicsBody else { diff --git a/star-dash/star-dash/Rendering/SyncModule/SpriteModule.swift b/star-dash/star-dash/Rendering/SyncModule/SpriteModule.swift index 70a77c69..5f0c284d 100644 --- a/star-dash/star-dash/Rendering/SyncModule/SpriteModule.swift +++ b/star-dash/star-dash/Rendering/SyncModule/SpriteModule.swift @@ -7,20 +7,19 @@ class SpriteModule: SyncModule { init(entityManager: EntityManager) { self.entityManager = entityManager } - + func sync(entity: Entity, into object: SDObject) { 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) - } + //if spriteComponent.image != spriteObject.activeTexture { + // spriteObject.runTexture(named: spriteComponent.image) + //} } func create(for object: SDObject, from entity: Entity) { - return } } @@ -28,7 +27,9 @@ extension SpriteModule: CreationModule { func createObject(from entity: Entity) -> SDObject? { var newObject = SDObject() if let spriteComponent = entityManager.component(ofType: SpriteComponent.self, of: entity.id) { - newObject = SDSpriteObject(imageNamed: "PlayerRedNose") + 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 { diff --git a/star-dash/star-dash/ViewController.swift b/star-dash/star-dash/ViewController.swift index 39eeb58b..fe2d5c99 100644 --- a/star-dash/star-dash/ViewController.swift +++ b/star-dash/star-dash/ViewController.swift @@ -20,12 +20,13 @@ class ViewController: UIViewController { let scene = GameScene(size: CGSize(width: 4_842, height: 1_040)) scene.scaleMode = .aspectFill + scene.sceneDelegate = self self.scene = scene let gameEngine = GameEngine() self.gameEngine = gameEngine - self.renderSynchronizer = RenderSynchronizer(entityManager: gameEngine.entityManager) + self.renderSynchronizer = RenderSynchronizer(entityManager: gameEngine.entityManager, scene: scene) - //setupGame() + // setupGame() setupGameEntities() guard let renderer = MTKRenderer(scene: scene) else { @@ -36,8 +37,9 @@ class ViewController: UIViewController { self.renderer = renderer } - func setupGame() { - guard let scene = self.scene else { + func setupGameEntities() { + guard let scene = self.scene, + let entityManager = gameEngine?.entityManager else { return } @@ -46,43 +48,26 @@ class ViewController: UIViewController { background.zPosition = -1 scene.addObject(background) - let ball = SDSpriteObject(imageNamed: "PlayerRedNose") - ball.size = CGSize(width: 100, height: 140) - ball.physicsBody = SDPhysicsBody(rectangleOf: CGSize(width: 60, height: 110)) - ball.position = CGPoint(x: scene.size.width / 2, y: scene.size.height / 2 + 200) - scene.addObject(ball) - - // let textureAtlas = SKTextureAtlas(named: "PlayerRedNoseRun") - // var frames = [SKTexture]() - // for idx in 0.. Date: Sat, 16 Mar 2024 21:40:11 +0800 Subject: [PATCH 13/21] refactor: Refactor to create a GameBridge component --- star-dash/star-dash.xcodeproj/project.pbxproj | 18 +++++++++++++----- .../GameBridge.swift} | 8 ++++++-- .../SyncModule/CreationModule.swift | 0 .../SyncModule/ObjectModule.swift | 0 .../SyncModule/PhysicsModule.swift | 0 .../SyncModule/SpriteModule.swift | 0 .../SyncModule/SyncModule.swift | 0 star-dash/star-dash/ViewController.swift | 11 +++++------ 8 files changed, 24 insertions(+), 13 deletions(-) rename star-dash/star-dash/{Rendering/RenderSynchronizer.swift => GameBridge/GameBridge.swift} (94%) rename star-dash/star-dash/{Rendering => GameBridge}/SyncModule/CreationModule.swift (100%) rename star-dash/star-dash/{Rendering => GameBridge}/SyncModule/ObjectModule.swift (100%) rename star-dash/star-dash/{Rendering => GameBridge}/SyncModule/PhysicsModule.swift (100%) rename star-dash/star-dash/{Rendering => GameBridge}/SyncModule/SpriteModule.swift (100%) rename star-dash/star-dash/{Rendering => GameBridge}/SyncModule/SyncModule.swift (100%) diff --git a/star-dash/star-dash.xcodeproj/project.pbxproj b/star-dash/star-dash.xcodeproj/project.pbxproj index a68cac4c..55facf58 100644 --- a/star-dash/star-dash.xcodeproj/project.pbxproj +++ b/star-dash/star-dash.xcodeproj/project.pbxproj @@ -46,7 +46,7 @@ E64361122BA4C2CD003850FD /* ObjectModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E643610C2BA4C2CC003850FD /* ObjectModule.swift */; }; E64361132BA4C2CD003850FD /* PhysicsModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E643610D2BA4C2CC003850FD /* PhysicsModule.swift */; }; E64361142BA4C2CD003850FD /* CreationModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E643610E2BA4C2CC003850FD /* CreationModule.swift */; }; - E64361152BA4C2CD003850FD /* RenderSynchronizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E643610F2BA4C2CC003850FD /* RenderSynchronizer.swift */; }; + E64361152BA4C2CD003850FD /* GameBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = E643610F2BA4C2CC003850FD /* GameBridge.swift */; }; E6A745162BA057040080C1BE /* MTKRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A745112BA057040080C1BE /* MTKRenderer.swift */; }; E6A745172BA057040080C1BE /* ControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A745122BA057040080C1BE /* ControlView.swift */; }; E6A745182BA057040080C1BE /* Renderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A745132BA057040080C1BE /* Renderer.swift */; }; @@ -130,7 +130,7 @@ E643610C2BA4C2CC003850FD /* ObjectModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectModule.swift; sourceTree = ""; }; E643610D2BA4C2CC003850FD /* PhysicsModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhysicsModule.swift; sourceTree = ""; }; E643610E2BA4C2CC003850FD /* CreationModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreationModule.swift; sourceTree = ""; }; - E643610F2BA4C2CC003850FD /* RenderSynchronizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RenderSynchronizer.swift; sourceTree = ""; }; + E643610F2BA4C2CC003850FD /* GameBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameBridge.swift; sourceTree = ""; }; E6A745112BA057040080C1BE /* MTKRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MTKRenderer.swift; sourceTree = ""; }; E6A745122BA057040080C1BE /* ControlView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlView.swift; sourceTree = ""; }; E6A745132BA057040080C1BE /* Renderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Renderer.swift; sourceTree = ""; }; @@ -226,6 +226,7 @@ 4E630EF42B9F7E070008F887 /* star-dash */ = { isa = PBXGroup; children = ( + E6A011152BA5D111006904D9 /* GameBridge */, 46B8C0982BA328BF00498705 /* GameEngine */, E6A745102BA057040080C1BE /* Rendering */, 4E86605D2BA095CC0035530D /* Constants */, @@ -321,11 +322,18 @@ path = SyncModule; sourceTree = ""; }; - E6A745102BA057040080C1BE /* Rendering */ = { + E6A011152BA5D111006904D9 /* GameBridge */ = { isa = PBXGroup; children = ( - E643610F2BA4C2CC003850FD /* RenderSynchronizer.swift */, + E643610F2BA4C2CC003850FD /* GameBridge.swift */, E64361092BA4C2CC003850FD /* SyncModule */, + ); + path = GameBridge; + sourceTree = ""; + }; + E6A745102BA057040080C1BE /* Rendering */ = { + isa = PBXGroup; + children = ( E6B5509F2BA15D2000DC7396 /* MTKRenderer */, E6A745132BA057040080C1BE /* Renderer.swift */, ); @@ -534,7 +542,7 @@ 4E630EF62B9F7E070008F887 /* AppDelegate.swift in Sources */, 461148962BA1D53D0073E7E1 /* PositionSystem.swift in Sources */, 4E630F2A2B9F7EF60008F887 /* PositionComponent.swift in Sources */, - E64361152BA4C2CD003850FD /* RenderSynchronizer.swift in Sources */, + E64361152BA4C2CD003850FD /* GameBridge.swift in Sources */, 14E247952BA2CB480071FFC0 /* EventManager.swift in Sources */, 4E630EF82B9F7E070008F887 /* SceneDelegate.swift in Sources */, E64361112BA4C2CD003850FD /* SyncModule.swift in Sources */, diff --git a/star-dash/star-dash/Rendering/RenderSynchronizer.swift b/star-dash/star-dash/GameBridge/GameBridge.swift similarity index 94% rename from star-dash/star-dash/Rendering/RenderSynchronizer.swift rename to star-dash/star-dash/GameBridge/GameBridge.swift index 6652c3fc..909ae0e1 100644 --- a/star-dash/star-dash/Rendering/RenderSynchronizer.swift +++ b/star-dash/star-dash/GameBridge/GameBridge.swift @@ -1,6 +1,6 @@ import SDPhysicsEngine -class RenderSynchronizer { +class GameBridge { var entityManager: EntityManager var scene: SDScene @@ -18,7 +18,7 @@ class RenderSynchronizer { registerModules() } - func sync() { + func syncFromEntities() { var toRemove = Set(entitiesMap.keys) for entity in entityManager.entityMap.values { @@ -34,6 +34,10 @@ class RenderSynchronizer { removeObject(from: entityId) } } + + func syncToEntities() { + + } func registerModule(_ module: SyncModule) { modules.append(module) diff --git a/star-dash/star-dash/Rendering/SyncModule/CreationModule.swift b/star-dash/star-dash/GameBridge/SyncModule/CreationModule.swift similarity index 100% rename from star-dash/star-dash/Rendering/SyncModule/CreationModule.swift rename to star-dash/star-dash/GameBridge/SyncModule/CreationModule.swift diff --git a/star-dash/star-dash/Rendering/SyncModule/ObjectModule.swift b/star-dash/star-dash/GameBridge/SyncModule/ObjectModule.swift similarity index 100% rename from star-dash/star-dash/Rendering/SyncModule/ObjectModule.swift rename to star-dash/star-dash/GameBridge/SyncModule/ObjectModule.swift diff --git a/star-dash/star-dash/Rendering/SyncModule/PhysicsModule.swift b/star-dash/star-dash/GameBridge/SyncModule/PhysicsModule.swift similarity index 100% rename from star-dash/star-dash/Rendering/SyncModule/PhysicsModule.swift rename to star-dash/star-dash/GameBridge/SyncModule/PhysicsModule.swift diff --git a/star-dash/star-dash/Rendering/SyncModule/SpriteModule.swift b/star-dash/star-dash/GameBridge/SyncModule/SpriteModule.swift similarity index 100% rename from star-dash/star-dash/Rendering/SyncModule/SpriteModule.swift rename to star-dash/star-dash/GameBridge/SyncModule/SpriteModule.swift diff --git a/star-dash/star-dash/Rendering/SyncModule/SyncModule.swift b/star-dash/star-dash/GameBridge/SyncModule/SyncModule.swift similarity index 100% rename from star-dash/star-dash/Rendering/SyncModule/SyncModule.swift rename to star-dash/star-dash/GameBridge/SyncModule/SyncModule.swift diff --git a/star-dash/star-dash/ViewController.swift b/star-dash/star-dash/ViewController.swift index fe2d5c99..3cfed500 100644 --- a/star-dash/star-dash/ViewController.swift +++ b/star-dash/star-dash/ViewController.swift @@ -12,7 +12,7 @@ class ViewController: UIViewController { var scene: SDScene? var renderer: Renderer? - var renderSynchronizer: RenderSynchronizer? + var gameBridge: GameBridge? var gameEngine: GameEngine? override func viewDidLoad() { @@ -24,7 +24,7 @@ class ViewController: UIViewController { self.scene = scene let gameEngine = GameEngine() self.gameEngine = gameEngine - self.renderSynchronizer = RenderSynchronizer(entityManager: gameEngine.entityManager, scene: scene) + self.gameBridge = GameBridge(entityManager: gameEngine.entityManager, scene: scene) // setupGame() setupGameEntities() @@ -65,9 +65,8 @@ class ViewController: UIViewController { extension ViewController: SDSceneDelegate { func update(_ scene: SDScene, deltaTime: Double) { - // TODO: Sync SDObjects into Entities - // TODO: Update Game Logic - // TODO: Sync Entities into SDObjects - renderSynchronizer?.sync() + gameBridge?.syncToEntities() + gameEngine?.update(by: deltaTime) + gameBridge?.syncFromEntities() } } From e0fff745ec7a453066cd1302e5e4936b4b125077 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sat, 16 Mar 2024 22:23:37 +0800 Subject: [PATCH 14/21] chore: Resolve swiftlint issues --- SDPhysicsEngine/Package.swift | 4 ++-- .../SDPhysicsEngine/Object/SDObject.swift | 4 ++-- .../SDPhysicsEngine/Object/SDPhysicsBody.swift | 6 +++--- .../Object/SDSpriteObject.swift | 4 ++-- .../Sources/SDPhysicsEngine/SDScene.swift | 2 +- .../SDPhysicsEngine/SDSceneDelegate.swift | 2 +- .../star-dash/GameBridge/GameBridge.swift | 18 ++++++++++++++---- .../GameBridge/SyncModule/ObjectModule.swift | 11 ++++++++++- .../GameBridge/SyncModule/PhysicsModule.swift | 16 ++++++++++++++-- .../GameBridge/SyncModule/SpriteModule.swift | 11 +++++++---- .../GameBridge/SyncModule/SyncModule.swift | 5 +++-- 11 files changed, 59 insertions(+), 24 deletions(-) diff --git a/SDPhysicsEngine/Package.swift b/SDPhysicsEngine/Package.swift index 1aada652..c75546f0 100644 --- a/SDPhysicsEngine/Package.swift +++ b/SDPhysicsEngine/Package.swift @@ -9,7 +9,7 @@ let package = Package( // Products define the executables and libraries a package produces, making them visible to other packages. .library( name: "SDPhysicsEngine", - targets: ["SDPhysicsEngine"]), + targets: ["SDPhysicsEngine"]) ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. @@ -18,6 +18,6 @@ let package = Package( name: "SDPhysicsEngine"), .testTarget( name: "SDPhysicsEngineTests", - dependencies: ["SDPhysicsEngine"]), + dependencies: ["SDPhysicsEngine"]) ] ) diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDObject.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDObject.swift index 51910710..7eb44ce6 100644 --- a/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDObject.swift +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDObject.swift @@ -2,7 +2,7 @@ import SpriteKit public class SDObject { let node: SKNode - + var innerRotation: CGFloat = 0 public init() { @@ -22,7 +22,7 @@ public class SDObject { get { node.zPosition } set { node.zPosition = newValue } } - + public var rotation: CGFloat { get { innerRotation } set { diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDPhysicsBody.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDPhysicsBody.swift index bdf90820..11878abe 100644 --- a/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDPhysicsBody.swift +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDPhysicsBody.swift @@ -20,10 +20,10 @@ public class SDPhysicsBody { get { body.velocity } set { body.velocity = newValue } } - + public var force: CGVector { - //get { body.force } - //set { body.force = newValue} + // get { body.force } + // set { body.force = newValue} get { CGVector(dx: 0, dy: 0) } set { } } diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDSpriteObject.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDSpriteObject.swift index 60835fab..f6954771 100644 --- a/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDSpriteObject.swift +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDSpriteObject.swift @@ -4,12 +4,12 @@ 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 } diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/SDScene.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/SDScene.swift index ccb5a73e..79f0fe83 100644 --- a/SDPhysicsEngine/Sources/SDPhysicsEngine/SDScene.swift +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/SDScene.swift @@ -1,7 +1,7 @@ import CoreGraphics public protocol SDScene { - + var size: CGSize { get } func addObject(_ object: SDObject) diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/SDSceneDelegate.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/SDSceneDelegate.swift index 76b6d930..e1e4dba3 100644 --- a/SDPhysicsEngine/Sources/SDPhysicsEngine/SDSceneDelegate.swift +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/SDSceneDelegate.swift @@ -1,4 +1,4 @@ public protocol SDSceneDelegate: AnyObject { - + func update(_ scene: SDScene, deltaTime: Double) } diff --git a/star-dash/star-dash/GameBridge/GameBridge.swift b/star-dash/star-dash/GameBridge/GameBridge.swift index 909ae0e1..8820b58c 100644 --- a/star-dash/star-dash/GameBridge/GameBridge.swift +++ b/star-dash/star-dash/GameBridge/GameBridge.swift @@ -24,7 +24,7 @@ class GameBridge { for entity in entityManager.entityMap.values { toRemove.remove(entity.id) if let object = entitiesMap[entity.id] { - //update(object: object, from: entity) + // update(object: object, from: entity) } else { createObject(from: entity) } @@ -34,9 +34,13 @@ class GameBridge { removeObject(from: entityId) } } - + func syncToEntities() { - + for (entityId, object) in entitiesMap { + if let entity = entityManager.entityMap[entityId] { + update(entity: entity, from: object) + } + } } func registerModule(_ module: SyncModule) { @@ -52,9 +56,15 @@ class GameBridge { 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(entity: entity, into: object) + $0.sync(object: object, from: entity) } } diff --git a/star-dash/star-dash/GameBridge/SyncModule/ObjectModule.swift b/star-dash/star-dash/GameBridge/SyncModule/ObjectModule.swift index 40e35df8..746249b0 100644 --- a/star-dash/star-dash/GameBridge/SyncModule/ObjectModule.swift +++ b/star-dash/star-dash/GameBridge/SyncModule/ObjectModule.swift @@ -8,7 +8,16 @@ class ObjectModule: SyncModule { self.entityManager = entityManager } - func sync(entity: Entity, into object: SDObject) { + 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: Float(object.rotation)) + } + + func sync(object: SDObject, from entity: Entity) { guard let positionComponent = entityManager.component(ofType: PositionComponent.self, of: entity.id) else { return } diff --git a/star-dash/star-dash/GameBridge/SyncModule/PhysicsModule.swift b/star-dash/star-dash/GameBridge/SyncModule/PhysicsModule.swift index a46ffae8..bef33135 100644 --- a/star-dash/star-dash/GameBridge/SyncModule/PhysicsModule.swift +++ b/star-dash/star-dash/GameBridge/SyncModule/PhysicsModule.swift @@ -8,7 +8,19 @@ class PhysicsModule: SyncModule { self.entityManager = entityManager } - func sync(entity: Entity, into object: SDObject) { + 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 @@ -29,6 +41,6 @@ class PhysicsModule: SyncModule { } private func createRectanglePhysicsBody(physicsComponent: PhysicsComponent) -> SDPhysicsBody { - return SDPhysicsBody(rectangleOf: CGSize(width: 50, height: 50)) + 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 index 5f0c284d..b8fc3475 100644 --- a/star-dash/star-dash/GameBridge/SyncModule/SpriteModule.swift +++ b/star-dash/star-dash/GameBridge/SyncModule/SpriteModule.swift @@ -8,15 +8,18 @@ class SpriteModule: SyncModule { self.entityManager = entityManager } - func sync(entity: Entity, into object: SDObject) { + 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) - //} + // if spriteComponent.image != spriteObject.activeTexture { + // spriteObject.runTexture(named: spriteComponent.image) + // } } func create(for object: SDObject, from entity: Entity) { diff --git a/star-dash/star-dash/GameBridge/SyncModule/SyncModule.swift b/star-dash/star-dash/GameBridge/SyncModule/SyncModule.swift index b16db1f3..e74df332 100644 --- a/star-dash/star-dash/GameBridge/SyncModule/SyncModule.swift +++ b/star-dash/star-dash/GameBridge/SyncModule/SyncModule.swift @@ -1,7 +1,8 @@ import SDPhysicsEngine protocol SyncModule { - - func sync(entity: Entity, into object: SDObject) + + func sync(entity: Entity, from object: SDObject) + func sync(object: SDObject, from entity: Entity) func create(for object: SDObject, from entity: Entity) } From 87d2956bf463c455364c1aadeba92714f94b3573 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sat, 16 Mar 2024 23:33:32 +0800 Subject: [PATCH 15/21] chore: Use an interface between GameBridge and GameEngine --- .../GameBridge/EntitySyncInterface.swift | 6 ++++++ star-dash/star-dash/GameBridge/GameBridge.swift | 8 ++++---- .../GameBridge/SyncModule/ObjectModule.swift | 4 ++-- .../GameBridge/SyncModule/PhysicsModule.swift | 4 ++-- .../GameBridge/SyncModule/SpriteModule.swift | 4 ++-- star-dash/star-dash/GameEngine/GameEngine.swift | 16 +++++++++++++++- star-dash/star-dash/ViewController.swift | 2 +- 7 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 star-dash/star-dash/GameBridge/EntitySyncInterface.swift diff --git a/star-dash/star-dash/GameBridge/EntitySyncInterface.swift b/star-dash/star-dash/GameBridge/EntitySyncInterface.swift new file mode 100644 index 00000000..af5525a9 --- /dev/null +++ b/star-dash/star-dash/GameBridge/EntitySyncInterface.swift @@ -0,0 +1,6 @@ +protocol EntitySyncInterface { + + var entities: [Entity] { get } + + func component(ofType type: T.Type, of entityId: EntityId) -> T? +} \ No newline at end of file diff --git a/star-dash/star-dash/GameBridge/GameBridge.swift b/star-dash/star-dash/GameBridge/GameBridge.swift index 8820b58c..ac13adc1 100644 --- a/star-dash/star-dash/GameBridge/GameBridge.swift +++ b/star-dash/star-dash/GameBridge/GameBridge.swift @@ -2,14 +2,14 @@ import SDPhysicsEngine class GameBridge { - var entityManager: EntityManager + var entityManager: EntitySyncInterface var scene: SDScene var entitiesMap: [EntityId: SDObject] var modules: [SyncModule] var creationModule: CreationModule? - init(entityManager: EntityManager, scene: SDScene) { + init(entityManager: EntitySyncInterface, scene: SDScene) { self.entityManager = entityManager self.scene = scene modules = [] @@ -21,7 +21,7 @@ class GameBridge { func syncFromEntities() { var toRemove = Set(entitiesMap.keys) - for entity in entityManager.entityMap.values { + for entity in entityManager.entities() { toRemove.remove(entity.id) if let object = entitiesMap[entity.id] { // update(object: object, from: entity) @@ -37,7 +37,7 @@ class GameBridge { func syncToEntities() { for (entityId, object) in entitiesMap { - if let entity = entityManager.entityMap[entityId] { + if let entity = entityManager.entity(of: entityId) { update(entity: entity, from: object) } } diff --git a/star-dash/star-dash/GameBridge/SyncModule/ObjectModule.swift b/star-dash/star-dash/GameBridge/SyncModule/ObjectModule.swift index 746249b0..0332da4e 100644 --- a/star-dash/star-dash/GameBridge/SyncModule/ObjectModule.swift +++ b/star-dash/star-dash/GameBridge/SyncModule/ObjectModule.swift @@ -2,9 +2,9 @@ import CoreGraphics import SDPhysicsEngine class ObjectModule: SyncModule { - let entityManager: EntityManager + let entityManager: EntitySyncInterface - init(entityManager: EntityManager) { + init(entityManager: EntitySyncInterface) { self.entityManager = entityManager } diff --git a/star-dash/star-dash/GameBridge/SyncModule/PhysicsModule.swift b/star-dash/star-dash/GameBridge/SyncModule/PhysicsModule.swift index bef33135..2ff1883c 100644 --- a/star-dash/star-dash/GameBridge/SyncModule/PhysicsModule.swift +++ b/star-dash/star-dash/GameBridge/SyncModule/PhysicsModule.swift @@ -2,9 +2,9 @@ import CoreGraphics import SDPhysicsEngine class PhysicsModule: SyncModule { - let entityManager: EntityManager + let entityManager: EntitySyncInterface - init(entityManager: EntityManager) { + init(entityManager: EntitySyncInterface) { self.entityManager = entityManager } diff --git a/star-dash/star-dash/GameBridge/SyncModule/SpriteModule.swift b/star-dash/star-dash/GameBridge/SyncModule/SpriteModule.swift index b8fc3475..a08c9705 100644 --- a/star-dash/star-dash/GameBridge/SyncModule/SpriteModule.swift +++ b/star-dash/star-dash/GameBridge/SyncModule/SpriteModule.swift @@ -2,9 +2,9 @@ import CoreGraphics import SDPhysicsEngine class SpriteModule: SyncModule { - let entityManager: EntityManager + let entityManager: EntitySyncInterface - init(entityManager: EntityManager) { + init(entityManager: EntitySyncInterface) { self.entityManager = entityManager } diff --git a/star-dash/star-dash/GameEngine/GameEngine.swift b/star-dash/star-dash/GameEngine/GameEngine.swift index c4c2ae0a..dd0e0067 100644 --- a/star-dash/star-dash/GameEngine/GameEngine.swift +++ b/star-dash/star-dash/GameEngine/GameEngine.swift @@ -16,7 +16,6 @@ class GameEngine { 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 { + + 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] + } + + func entities() -> [Entity] { + entityManager.entityMap.values + } +} diff --git a/star-dash/star-dash/ViewController.swift b/star-dash/star-dash/ViewController.swift index 3cfed500..3c1896d1 100644 --- a/star-dash/star-dash/ViewController.swift +++ b/star-dash/star-dash/ViewController.swift @@ -24,7 +24,7 @@ class ViewController: UIViewController { self.scene = scene let gameEngine = GameEngine() self.gameEngine = gameEngine - self.gameBridge = GameBridge(entityManager: gameEngine.entityManager, scene: scene) + self.gameBridge = GameBridge(entityManager: gameEngine, scene: scene) // setupGame() setupGameEntities() From 1047089b2a1119bd8e8f4bd5c0630f378dd7be91 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sat, 16 Mar 2024 23:42:44 +0800 Subject: [PATCH 16/21] fix: Fix syntax issues --- star-dash/star-dash.xcodeproj/project.pbxproj | 4 ++++ .../star-dash/GameBridge/EntitySyncInterface.swift | 5 +++-- star-dash/star-dash/GameBridge/GameBridge.swift | 2 +- star-dash/star-dash/GameEngine/GameEngine.swift | 10 +++++----- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/star-dash/star-dash.xcodeproj/project.pbxproj b/star-dash/star-dash.xcodeproj/project.pbxproj index 55facf58..ece18c43 100644 --- a/star-dash/star-dash.xcodeproj/project.pbxproj +++ b/star-dash/star-dash.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ E64361132BA4C2CD003850FD /* PhysicsModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E643610D2BA4C2CC003850FD /* PhysicsModule.swift */; }; E64361142BA4C2CD003850FD /* CreationModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E643610E2BA4C2CC003850FD /* CreationModule.swift */; }; E64361152BA4C2CD003850FD /* GameBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = E643610F2BA4C2CC003850FD /* GameBridge.swift */; }; + E6A011172BA5F4AD006904D9 /* EntitySyncInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A011162BA5F4AD006904D9 /* EntitySyncInterface.swift */; }; E6A745162BA057040080C1BE /* MTKRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A745112BA057040080C1BE /* MTKRenderer.swift */; }; E6A745172BA057040080C1BE /* ControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A745122BA057040080C1BE /* ControlView.swift */; }; E6A745182BA057040080C1BE /* Renderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A745132BA057040080C1BE /* Renderer.swift */; }; @@ -131,6 +132,7 @@ E643610D2BA4C2CC003850FD /* PhysicsModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhysicsModule.swift; sourceTree = ""; }; E643610E2BA4C2CC003850FD /* CreationModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreationModule.swift; sourceTree = ""; }; E643610F2BA4C2CC003850FD /* GameBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameBridge.swift; sourceTree = ""; }; + E6A011162BA5F4AD006904D9 /* EntitySyncInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntitySyncInterface.swift; sourceTree = ""; }; E6A745112BA057040080C1BE /* MTKRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MTKRenderer.swift; sourceTree = ""; }; E6A745122BA057040080C1BE /* ControlView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlView.swift; sourceTree = ""; }; E6A745132BA057040080C1BE /* Renderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Renderer.swift; sourceTree = ""; }; @@ -325,6 +327,7 @@ E6A011152BA5D111006904D9 /* GameBridge */ = { isa = PBXGroup; children = ( + E6A011162BA5F4AD006904D9 /* EntitySyncInterface.swift */, E643610F2BA4C2CC003850FD /* GameBridge.swift */, E64361092BA4C2CC003850FD /* SyncModule */, ); @@ -537,6 +540,7 @@ 461148912BA1CDBF0073E7E1 /* SystemManager.swift in Sources */, 145F2C842BA22CA300457549 /* EventModifiable.swift in Sources */, 4E630EFA2B9F7E070008F887 /* ViewController.swift in Sources */, + E6A011172BA5F4AD006904D9 /* EntitySyncInterface.swift in Sources */, E64361132BA4C2CD003850FD /* PhysicsModule.swift in Sources */, 4E630F322B9F887C0008F887 /* PhysicsComponent.swift in Sources */, 4E630EF62B9F7E070008F887 /* AppDelegate.swift in Sources */, diff --git a/star-dash/star-dash/GameBridge/EntitySyncInterface.swift b/star-dash/star-dash/GameBridge/EntitySyncInterface.swift index af5525a9..49675865 100644 --- a/star-dash/star-dash/GameBridge/EntitySyncInterface.swift +++ b/star-dash/star-dash/GameBridge/EntitySyncInterface.swift @@ -1,6 +1,7 @@ protocol EntitySyncInterface { - + var entities: [Entity] { get } func component(ofType type: T.Type, of entityId: EntityId) -> T? -} \ No newline at end of file + func entity(of: EntityId) -> Entity? +} diff --git a/star-dash/star-dash/GameBridge/GameBridge.swift b/star-dash/star-dash/GameBridge/GameBridge.swift index ac13adc1..ea00a7eb 100644 --- a/star-dash/star-dash/GameBridge/GameBridge.swift +++ b/star-dash/star-dash/GameBridge/GameBridge.swift @@ -21,7 +21,7 @@ class GameBridge { func syncFromEntities() { var toRemove = Set(entitiesMap.keys) - for entity in entityManager.entities() { + for entity in entityManager.entities { toRemove.remove(entity.id) if let object = entitiesMap[entity.id] { // update(object: object, from: entity) diff --git a/star-dash/star-dash/GameEngine/GameEngine.swift b/star-dash/star-dash/GameEngine/GameEngine.swift index dd0e0067..542bf3fa 100644 --- a/star-dash/star-dash/GameEngine/GameEngine.swift +++ b/star-dash/star-dash/GameEngine/GameEngine.swift @@ -36,16 +36,16 @@ extension GameEngine: EventModifiable { } extension GameEngine: EntitySyncInterface { + + var entities: [Entity] { + get { 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 { + func entity(of entityId: EntityId) -> Entity? { entityManager.entityMap[entityId] } - - func entities() -> [Entity] { - entityManager.entityMap.values - } } From d917d9515e0b866d740b78b97b470d230bf7768d Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sat, 16 Mar 2024 23:54:29 +0800 Subject: [PATCH 17/21] chore: Resolve swiftlint issues --- .../SDPhysicsEngine/Object/SDPhysicsBody.swift | 5 ++--- .../SDPhysicsEngineTests/SDPhysicsEngineTests.swift | 12 ------------ .../star-dash/GameBridge/EntitySyncInterface.swift | 2 +- .../GameBridge/SyncModule/PhysicsModule.swift | 4 ++-- star-dash/star-dash/GameEngine/GameEngine.swift | 4 ++-- 5 files changed, 7 insertions(+), 20 deletions(-) delete mode 100644 SDPhysicsEngine/Tests/SDPhysicsEngineTests/SDPhysicsEngineTests.swift diff --git a/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDPhysicsBody.swift b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDPhysicsBody.swift index 11878abe..b3f26a22 100644 --- a/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDPhysicsBody.swift +++ b/SDPhysicsEngine/Sources/SDPhysicsEngine/Object/SDPhysicsBody.swift @@ -23,9 +23,8 @@ public class SDPhysicsBody { public var force: CGVector { // get { body.force } - // set { body.force = newValue} - get { CGVector(dx: 0, dy: 0) } - set { } + // set { body.force = newValue } + CGVector(dx: 0, dy: 0) } public var affectedByGravity: Bool { diff --git a/SDPhysicsEngine/Tests/SDPhysicsEngineTests/SDPhysicsEngineTests.swift b/SDPhysicsEngine/Tests/SDPhysicsEngineTests/SDPhysicsEngineTests.swift deleted file mode 100644 index d51f45b3..00000000 --- a/SDPhysicsEngine/Tests/SDPhysicsEngineTests/SDPhysicsEngineTests.swift +++ /dev/null @@ -1,12 +0,0 @@ -import XCTest -@testable import SDPhysicsEngine - -final class SDPhysicsEngineTests: XCTestCase { - func testExample() throws { - // XCTest Documentation - // https://developer.apple.com/documentation/xctest - - // Defining Test Cases and Test Methods - // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods - } -} diff --git a/star-dash/star-dash/GameBridge/EntitySyncInterface.swift b/star-dash/star-dash/GameBridge/EntitySyncInterface.swift index 49675865..d4ebd3cc 100644 --- a/star-dash/star-dash/GameBridge/EntitySyncInterface.swift +++ b/star-dash/star-dash/GameBridge/EntitySyncInterface.swift @@ -1,5 +1,5 @@ protocol EntitySyncInterface { - + var entities: [Entity] { get } func component(ofType type: T.Type, of entityId: EntityId) -> T? diff --git a/star-dash/star-dash/GameBridge/SyncModule/PhysicsModule.swift b/star-dash/star-dash/GameBridge/SyncModule/PhysicsModule.swift index 2ff1883c..21432119 100644 --- a/star-dash/star-dash/GameBridge/SyncModule/PhysicsModule.swift +++ b/star-dash/star-dash/GameBridge/SyncModule/PhysicsModule.swift @@ -16,7 +16,7 @@ class PhysicsModule: SyncModule { physicsComponent.mass = body.mass physicsComponent.velocity = body.velocity - physicsComponent.force = body.force + // physicsComponent.force = body.force physicsComponent.affectedByGravity = body.affectedByGravity } @@ -28,7 +28,7 @@ class PhysicsModule: SyncModule { body.mass = physicsComponent.mass body.velocity = physicsComponent.velocity - body.force = physicsComponent.force + // body.force = physicsComponent.force body.affectedByGravity = physicsComponent.affectedByGravity } diff --git a/star-dash/star-dash/GameEngine/GameEngine.swift b/star-dash/star-dash/GameEngine/GameEngine.swift index 542bf3fa..80bc3556 100644 --- a/star-dash/star-dash/GameEngine/GameEngine.swift +++ b/star-dash/star-dash/GameEngine/GameEngine.swift @@ -36,9 +36,9 @@ extension GameEngine: EventModifiable { } extension GameEngine: EntitySyncInterface { - + var entities: [Entity] { - get { Array(entityManager.entityMap.values) } + Array(entityManager.entityMap.values) } func component(ofType type: T.Type, of entityId: EntityId) -> T? { From 6e5e6979959d494519a29a6d3e5a111a0952e860 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sat, 16 Mar 2024 23:56:26 +0800 Subject: [PATCH 18/21] chore: Remove commented code --- star-dash/star-dash/GameBridge/GameBridge.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/star-dash/star-dash/GameBridge/GameBridge.swift b/star-dash/star-dash/GameBridge/GameBridge.swift index ea00a7eb..01942a32 100644 --- a/star-dash/star-dash/GameBridge/GameBridge.swift +++ b/star-dash/star-dash/GameBridge/GameBridge.swift @@ -24,7 +24,7 @@ class GameBridge { for entity in entityManager.entities { toRemove.remove(entity.id) if let object = entitiesMap[entity.id] { - // update(object: object, from: entity) + update(object: object, from: entity) } else { createObject(from: entity) } From e89435099564b564f0b282b1f100002a30c8d26d Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sun, 17 Mar 2024 02:03:40 +0800 Subject: [PATCH 19/21] chore: Use CGFloat for PositionComponent's rotation --- star-dash/star-dash/GameBridge/SyncModule/ObjectModule.swift | 4 ++-- .../star-dash/GameEngine/Components/PositionComponent.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/star-dash/star-dash/GameBridge/SyncModule/ObjectModule.swift b/star-dash/star-dash/GameBridge/SyncModule/ObjectModule.swift index 0332da4e..49c540a6 100644 --- a/star-dash/star-dash/GameBridge/SyncModule/ObjectModule.swift +++ b/star-dash/star-dash/GameBridge/SyncModule/ObjectModule.swift @@ -14,7 +14,7 @@ class ObjectModule: SyncModule { } positionComponent.setPosition(position: object.position) - positionComponent.setRotation(rotation: Float(object.rotation)) + positionComponent.setRotation(rotation: object.rotation) } func sync(object: SDObject, from entity: Entity) { @@ -23,7 +23,7 @@ class ObjectModule: SyncModule { } object.position = positionComponent.position - object.rotation = CGFloat(positionComponent.rotation) + object.rotation = positionComponent.rotation } 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..29d34383 100644 --- a/star-dash/star-dash/GameEngine/Components/PositionComponent.swift +++ b/star-dash/star-dash/GameEngine/Components/PositionComponent.swift @@ -9,7 +9,7 @@ import Foundation class PositionComponent: Component { var position: CGPoint - var rotation: Float + var rotation: CGFloat init(id: UUID, entityId: UUID, position: CGPoint, rotation: Float) { self.position = position From f57f202fdcc4c69f51e6aee2aed81cee4bff1dd8 Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sun, 17 Mar 2024 02:04:49 +0800 Subject: [PATCH 20/21] chore: Make private methods private --- star-dash/star-dash/GameBridge/GameBridge.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/star-dash/star-dash/GameBridge/GameBridge.swift b/star-dash/star-dash/GameBridge/GameBridge.swift index 01942a32..815171f2 100644 --- a/star-dash/star-dash/GameBridge/GameBridge.swift +++ b/star-dash/star-dash/GameBridge/GameBridge.swift @@ -43,7 +43,7 @@ class GameBridge { } } - func registerModule(_ module: SyncModule) { + private func registerModule(_ module: SyncModule) { modules.append(module) } From aab969f6350456ea85d72a9989b0749431eea23e Mon Sep 17 00:00:00 2001 From: Goh Jun Yi <54541329+Junyi00@users.noreply.github.com> Date: Sun, 17 Mar 2024 02:07:47 +0800 Subject: [PATCH 21/21] fix: Fix syntax issues --- .../star-dash/GameEngine/Components/PositionComponent.swift | 6 +++--- star-dash/star-dash/GameEngine/Systems/PositionSystem.swift | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/star-dash/star-dash/GameEngine/Components/PositionComponent.swift b/star-dash/star-dash/GameEngine/Components/PositionComponent.swift index 29d34383..12d83b14 100644 --- a/star-dash/star-dash/GameEngine/Components/PositionComponent.swift +++ b/star-dash/star-dash/GameEngine/Components/PositionComponent.swift @@ -11,13 +11,13 @@ class PositionComponent: Component { var position: CGPoint 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/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) }