diff --git a/star-dash/star-dash.xcodeproj/project.pbxproj b/star-dash/star-dash.xcodeproj/project.pbxproj index d4e1f390..868d487b 100644 --- a/star-dash/star-dash.xcodeproj/project.pbxproj +++ b/star-dash/star-dash.xcodeproj/project.pbxproj @@ -53,6 +53,19 @@ 4E3458E92BA75E7C00E817C6 /* SpriteComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E3458E82BA75E7C00E817C6 /* SpriteComponentTests.swift */; }; 4E3458EB2BA75E8B00E817C6 /* PhysicsComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E3458EA2BA75E8B00E817C6 /* PhysicsComponentTests.swift */; }; 4E3458ED2BA75F1200E817C6 /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E3458EC2BA75F1200E817C6 /* Util.swift */; }; + 4E59E2512BAB2EAE007B3FA7 /* StorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E59E2502BAB2EAE007B3FA7 /* StorageManager.swift */; }; + 4E59E2532BAB3061007B3FA7 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E59E2522BAB3061007B3FA7 /* Database.swift */; }; + 4E59E2562BAB33EC007B3FA7 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = 4E59E2552BAB33EC007B3FA7 /* SQLite */; }; + 4E59E2582BAB3B5B007B3FA7 /* data.json in Resources */ = {isa = PBXBuildFile; fileRef = 4E59E2572BAB3B5B007B3FA7 /* data.json */; }; + 4E59E25B2BAB3FDB007B3FA7 /* Level.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E59E25A2BAB3FDB007B3FA7 /* Level.swift */; }; + 4E59E25D2BAB405A007B3FA7 /* EntityPersistable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E59E25C2BAB405A007B3FA7 /* EntityPersistable.swift */; }; + 4E59E25F2BAB4134007B3FA7 /* EntityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E59E25E2BAB4134007B3FA7 /* EntityType.swift */; }; + 4E59E2612BAB42FD007B3FA7 /* LevelPersistable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E59E2602BAB42FD007B3FA7 /* LevelPersistable.swift */; }; + 4E59E2632BACB9E2007B3FA7 /* LevelData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E59E2622BACB9E2007B3FA7 /* LevelData.swift */; }; + 4E59E2662BADA79A007B3FA7 /* MonsterEntityPersistable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E59E2652BADA79A007B3FA7 /* MonsterEntityPersistable.swift */; }; + 4E59E2682BADA7B6007B3FA7 /* CollectibleEntityPersistable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E59E2672BADA7B6007B3FA7 /* CollectibleEntityPersistable.swift */; }; + 4E59E26A2BADA7C7007B3FA7 /* ToolEntityPersistable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E59E2692BADA7C7007B3FA7 /* ToolEntityPersistable.swift */; }; + 4E59E26C2BADA7DD007B3FA7 /* ObstacleEntityPersistable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E59E26B2BADA7DD007B3FA7 /* ObstacleEntityPersistable.swift */; }; 4E630EF62B9F7E070008F887 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E630EF52B9F7E070008F887 /* AppDelegate.swift */; }; 4E630EF82B9F7E070008F887 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E630EF72B9F7E070008F887 /* SceneDelegate.swift */; }; 4E630EFA2B9F7E070008F887 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E630EF92B9F7E070008F887 /* ViewController.swift */; }; @@ -178,6 +191,18 @@ 4E3458E82BA75E7C00E817C6 /* SpriteComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpriteComponentTests.swift; sourceTree = ""; }; 4E3458EA2BA75E8B00E817C6 /* PhysicsComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhysicsComponentTests.swift; sourceTree = ""; }; 4E3458EC2BA75F1200E817C6 /* Util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Util.swift; sourceTree = ""; }; + 4E59E2502BAB2EAE007B3FA7 /* StorageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageManager.swift; sourceTree = ""; }; + 4E59E2522BAB3061007B3FA7 /* Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = ""; }; + 4E59E2572BAB3B5B007B3FA7 /* data.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = data.json; sourceTree = ""; }; + 4E59E25A2BAB3FDB007B3FA7 /* Level.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Level.swift; sourceTree = ""; }; + 4E59E25C2BAB405A007B3FA7 /* EntityPersistable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityPersistable.swift; sourceTree = ""; }; + 4E59E25E2BAB4134007B3FA7 /* EntityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityType.swift; sourceTree = ""; }; + 4E59E2602BAB42FD007B3FA7 /* LevelPersistable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LevelPersistable.swift; sourceTree = ""; }; + 4E59E2622BACB9E2007B3FA7 /* LevelData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LevelData.swift; sourceTree = ""; }; + 4E59E2652BADA79A007B3FA7 /* MonsterEntityPersistable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonsterEntityPersistable.swift; sourceTree = ""; }; + 4E59E2672BADA7B6007B3FA7 /* CollectibleEntityPersistable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectibleEntityPersistable.swift; sourceTree = ""; }; + 4E59E2692BADA7C7007B3FA7 /* ToolEntityPersistable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolEntityPersistable.swift; sourceTree = ""; }; + 4E59E26B2BADA7DD007B3FA7 /* ObstacleEntityPersistable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObstacleEntityPersistable.swift; sourceTree = ""; }; 4E630EF22B9F7E070008F887 /* star-dash.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "star-dash.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 4E630EF52B9F7E070008F887 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 4E630EF72B9F7E070008F887 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -235,6 +260,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4E59E2562BAB33EC007B3FA7 /* SQLite in Frameworks */, 14E247932BA2CA920071FFC0 /* DequeModule in Frameworks */, E6E4B8302BA6C8D4005ABAB1 /* SDPhysicsEngine in Frameworks */, ); @@ -392,6 +418,39 @@ path = Components; sourceTree = ""; }; + 4E59E24F2BAB2DBB007B3FA7 /* Persistence */ = { + isa = PBXGroup; + children = ( + 4E59E2642BAD9A06007B3FA7 /* Data */, + 4E59E2502BAB2EAE007B3FA7 /* StorageManager.swift */, + 4E59E2522BAB3061007B3FA7 /* Database.swift */, + 4E59E25C2BAB405A007B3FA7 /* EntityPersistable.swift */, + 4E59E2602BAB42FD007B3FA7 /* LevelPersistable.swift */, + 4E59E2652BADA79A007B3FA7 /* MonsterEntityPersistable.swift */, + 4E59E26B2BADA7DD007B3FA7 /* ObstacleEntityPersistable.swift */, + 4E59E2672BADA7B6007B3FA7 /* CollectibleEntityPersistable.swift */, + 4E59E2692BADA7C7007B3FA7 /* ToolEntityPersistable.swift */, + ); + path = Persistence; + sourceTree = ""; + }; + 4E59E2592BAB3FC9007B3FA7 /* Model */ = { + isa = PBXGroup; + children = ( + 4E59E25A2BAB3FDB007B3FA7 /* Level.swift */, + ); + path = Model; + sourceTree = ""; + }; + 4E59E2642BAD9A06007B3FA7 /* Data */ = { + isa = PBXGroup; + children = ( + 4E59E2572BAB3B5B007B3FA7 /* data.json */, + 4E59E2622BACB9E2007B3FA7 /* LevelData.swift */, + ); + path = Data; + sourceTree = ""; + }; 4E630EE92B9F7E070008F887 = { isa = PBXGroup; children = ( @@ -418,6 +477,8 @@ 4E630EF42B9F7E070008F887 /* star-dash */ = { isa = PBXGroup; children = ( + 4E59E2592BAB3FC9007B3FA7 /* Model */, + 4E59E24F2BAB2DBB007B3FA7 /* Persistence */, E6A011152BA5D111006904D9 /* GameBridge */, 46B8C0982BA328BF00498705 /* GameEngine */, E6A745102BA057040080C1BE /* Rendering */, @@ -485,6 +546,7 @@ isa = PBXGroup; children = ( 4E630F362B9F91DE0008F887 /* PlayerSprite.swift */, + 4E59E25E2BAB4134007B3FA7 /* EntityType.swift */, ); path = Enums; sourceTree = ""; @@ -588,6 +650,7 @@ packageProductDependencies = ( 14E247922BA2CA920071FFC0 /* DequeModule */, E6E4B82F2BA6C8D4005ABAB1 /* SDPhysicsEngine */, + 4E59E2552BAB33EC007B3FA7 /* SQLite */, ); productName = "star-dash"; productReference = 4E630EF22B9F7E070008F887 /* star-dash.app */; @@ -663,6 +726,7 @@ mainGroup = 4E630EE92B9F7E070008F887; packageReferences = ( 14E247912BA2CA920071FFC0 /* XCRemoteSwiftPackageReference "swift-collections" */, + 4E59E2542BAB33EC007B3FA7 /* XCRemoteSwiftPackageReference "SQLite.swift" */, ); productRefGroup = 4E630EF32B9F7E070008F887 /* Products */; projectDirPath = ""; @@ -684,6 +748,7 @@ 4E630F022B9F7E090008F887 /* LaunchScreen.storyboard in Resources */, 4E630EFF2B9F7E090008F887 /* Assets.xcassets in Resources */, 4E630EFD2B9F7E070008F887 /* Main.storyboard in Resources */, + 4E59E2582BAB3B5B007B3FA7 /* data.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -730,7 +795,9 @@ buildActionMask = 2147483647; files = ( 46D418182BA5CD840091A38B /* Player+Collidable.swift in Sources */, + 4E59E25B2BAB3FDB007B3FA7 /* Level.swift in Sources */, 46D4181E2BA5D2620091A38B /* Monster+Collidable.swift in Sources */, + 4E59E2662BADA79A007B3FA7 /* MonsterEntityPersistable.swift in Sources */, E69EE9342BAC6CC300033AB5 /* OverlayInfo.swift in Sources */, E6B1DC902BA34A4800473563 /* SDPhysicsEngine in Sources */, 4E630F272B9F7E770008F887 /* Entity.swift in Sources */, @@ -749,24 +816,30 @@ 46D4181A2BA5CDBD0091A38B /* CollisionHandler.swift in Sources */, 4E86605F2BA095E30035530D /* Collectible.swift in Sources */, E64361122BA4C2CD003850FD /* ObjectModule.swift in Sources */, + 4E59E2532BAB3061007B3FA7 /* Database.swift in Sources */, 4E8660662BA097D40035530D /* PhysicsConstants.swift in Sources */, 143AA3952BA4DF1C009C28E7 /* MonsterAttackPlayerEvent.swift in Sources */, 143AA3932BA4DBE7009C28E7 /* PlayerMonsterContactEvent.swift in Sources */, + 4E59E2682BADA7B6007B3FA7 /* CollectibleEntityPersistable.swift in Sources */, 46D418162BA5CBD60091A38B /* Collidable.swift in Sources */, 461148912BA1CDBF0073E7E1 /* SystemManager.swift in Sources */, + 4E59E2512BAB2EAE007B3FA7 /* StorageManager.swift in Sources */, 145F2C842BA22CA300457549 /* EventModifiable.swift in Sources */, 4E630EFA2B9F7E070008F887 /* ViewController.swift in Sources */, E6A011172BA5F4AD006904D9 /* EntitySyncInterface.swift in Sources */, E6B0AAD52BAAE4EC009CB939 /* PlayerFloorContactEvent.swift in Sources */, E64361132BA4C2CD003850FD /* PhysicsModule.swift in Sources */, 4E630F322B9F887C0008F887 /* PhysicsComponent.swift in Sources */, + 4E59E25D2BAB405A007B3FA7 /* EntityPersistable.swift in Sources */, 1471B0AD2BA6AE4E00878B14 /* UseGrappleHookEvent.swift in Sources */, + 4E59E26C2BADA7DD007B3FA7 /* ObstacleEntityPersistable.swift in Sources */, 4E630EF62B9F7E070008F887 /* AppDelegate.swift in Sources */, 461148962BA1D53D0073E7E1 /* PositionSystem.swift in Sources */, 143AA38C2BA4D3E3009C28E7 /* JumpEvent.swift in Sources */, 4E630F2A2B9F7EF60008F887 /* PositionComponent.swift in Sources */, 46D418132BA5C9930091A38B /* Floor.swift in Sources */, E64361152BA4C2CD003850FD /* GameBridge.swift in Sources */, + 4E59E26A2BADA7C7007B3FA7 /* ToolEntityPersistable.swift in Sources */, E6B0AAD32BAAE438009CB939 /* ControlViewDelegate.swift in Sources */, 4604BBD92BA81C940078B84C /* InventorySystem.swift in Sources */, 14E247952BA2CB480071FFC0 /* EventManager.swift in Sources */, @@ -784,8 +857,10 @@ 143AA3972BA4E0D9009C28E7 /* PickupCollectibleEvent.swift in Sources */, 46D418242BA5D5280091A38B /* Tool+Collidable.swift in Sources */, E64361142BA4C2CD003850FD /* CreationModule.swift in Sources */, + 4E59E2612BAB42FD007B3FA7 /* LevelPersistable.swift in Sources */, E6A745162BA057040080C1BE /* MTKRenderer.swift in Sources */, E6A745182BA057040080C1BE /* Renderer.swift in Sources */, + 4E59E25F2BAB4134007B3FA7 /* EntityType.swift in Sources */, E6A745172BA057040080C1BE /* ControlView.swift in Sources */, 14970F562BA8177B00CC1E8A /* GameConstants.swift in Sources */, 1471B0A42BA6AAF200878B14 /* PlayerDeathEvent.swift in Sources */, @@ -805,6 +880,7 @@ 461148982BA1E41F0073E7E1 /* PhysicsSystem.swift in Sources */, 4E630F2E2B9F81850008F887 /* HealthComponent.swift in Sources */, 4E86605C2BA095460035530D /* Monster.swift in Sources */, + 4E59E2632BACB9E2007B3FA7 /* LevelData.swift in Sources */, 145F2C802BA203B400457549 /* Event.swift in Sources */, 1471B0A22BA6AA2200878B14 /* RespawnEvent.swift in Sources */, 46D418112BA5C88B0091A38B /* Wall.swift in Sources */, @@ -1187,6 +1263,14 @@ minimumVersion = 1.1.0; }; }; + 4E59E2542BAB33EC007B3FA7 /* XCRemoteSwiftPackageReference "SQLite.swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/stephencelis/SQLite.swift.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.15.0; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -1195,6 +1279,11 @@ package = 14E247912BA2CA920071FFC0 /* XCRemoteSwiftPackageReference "swift-collections" */; productName = DequeModule; }; + 4E59E2552BAB33EC007B3FA7 /* SQLite */ = { + isa = XCSwiftPackageProductDependency; + package = 4E59E2542BAB33EC007B3FA7 /* XCRemoteSwiftPackageReference "SQLite.swift" */; + productName = SQLite; + }; E6B1DC8E2BA3459600473563 /* SDPhysicsEngine */ = { isa = XCSwiftPackageProductDependency; productName = SDPhysicsEngine; diff --git a/star-dash/star-dash.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/star-dash/star-dash.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1a0f4251..3fdf1830 100644 --- a/star-dash/star-dash.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/star-dash/star-dash.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,14 @@ { "pins" : [ + { + "identity" : "sqlite.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/stephencelis/SQLite.swift.git", + "state" : { + "revision" : "e78ae0220e17525a15ac68c697a155eb7a672a8e", + "version" : "0.15.0" + } + }, { "identity" : "swift-collections", "kind" : "remoteSourceControl", diff --git a/star-dash/star-dash/Enums/EntityType.swift b/star-dash/star-dash/Enums/EntityType.swift new file mode 100644 index 00000000..8df48447 --- /dev/null +++ b/star-dash/star-dash/Enums/EntityType.swift @@ -0,0 +1,14 @@ +// +// EntityType.swift +// star-dash +// +// Created by Lau Rui han on 21/3/24. +// + +import Foundation +enum EntityType: String, Encodable, Decodable { + case Monster = "monster" + case Collectible = "collectible" + case Obstacle = "obstacle" + case Tool = "tool" +} diff --git a/star-dash/star-dash/GameEngine/Entities/EntityManager.swift b/star-dash/star-dash/GameEngine/Entities/EntityManager.swift index 5df5b00e..8b3686cd 100644 --- a/star-dash/star-dash/GameEngine/Entities/EntityManager.swift +++ b/star-dash/star-dash/GameEngine/Entities/EntityManager.swift @@ -16,7 +16,6 @@ class EntityManager { var componentMap: ComponentMap var entityMap: EntityMap var entityComponentMap: EntityComponentMap - init(componentMap: ComponentMap, entityMap: EntityMap, entityComponentMap: EntityComponentMap) { self.componentMap = componentMap self.entityMap = entityMap diff --git a/star-dash/star-dash/GameEngine/Entities/GameEntities/Monster.swift b/star-dash/star-dash/GameEngine/Entities/GameEntities/Monster.swift index 1f3cef06..9e710602 100644 --- a/star-dash/star-dash/GameEngine/Entities/GameEntities/Monster.swift +++ b/star-dash/star-dash/GameEngine/Entities/GameEntities/Monster.swift @@ -10,19 +10,24 @@ import Foundation class Monster: Entity { let id: EntityId private let position: CGPoint - - init(id: EntityId, position: CGPoint) { + private let health: Int + private let sprite: String + private let size: CGSize + init(id: EntityId, position: CGPoint, health: Int, sprite: String, size: CGSize) { self.id = id self.position = position + self.health = health + self.sprite = sprite + self.size = size } - convenience init(position: CGPoint) { - self.init(id: UUID(), position: position) + convenience init(position: CGPoint, health: Int, sprite: String, size: CGSize) { + self.init(id: UUID(), position: position, health: health, sprite: sprite, size: size) } func setUpAndAdd(to: EntityManager) { let positionComponent = PositionComponent(entityId: self.id, position: self.position, rotation: .zero) - let healthComponent = HealthComponent(entityId: self.id, health: GameConstants.InitialHealth.monster) + let healthComponent = HealthComponent(entityId: self.id, health: self.health) let physicsComponent = PhysicsComponent(entityId: self.id, size: PhysicsConstants.Dimensions.monster) physicsComponent.collisionBitMask = PhysicsConstants.CollisionMask.monster physicsComponent.affectedByGravity = true diff --git a/star-dash/star-dash/GameEngine/Entities/GameEntities/Obstacle.swift b/star-dash/star-dash/GameEngine/Entities/GameEntities/Obstacle.swift index 117a8460..64df290f 100644 --- a/star-dash/star-dash/GameEngine/Entities/GameEntities/Obstacle.swift +++ b/star-dash/star-dash/GameEngine/Entities/GameEntities/Obstacle.swift @@ -10,14 +10,17 @@ import Foundation class Obstacle: Entity { let id: EntityId private let position: CGPoint - - init(id: EntityId, position: CGPoint) { + private let sprite: String + private let size: CGSize + init(id: EntityId, position: CGPoint, sprite: String, size: CGSize) { self.id = id self.position = position + self.sprite = sprite + self.size = size } - convenience init(position: CGPoint) { - self.init(id: UUID(), position: position) + convenience init(position: CGPoint, sprite: String, size: CGSize) { + self.init(id: UUID(), position: position, sprite: sprite, size: size) } func setUpAndAdd(to: EntityManager) { diff --git a/star-dash/star-dash/GameEngine/Entities/GameEntities/Tool.swift b/star-dash/star-dash/GameEngine/Entities/GameEntities/Tool.swift index 411810bd..6fdb0940 100644 --- a/star-dash/star-dash/GameEngine/Entities/GameEntities/Tool.swift +++ b/star-dash/star-dash/GameEngine/Entities/GameEntities/Tool.swift @@ -10,14 +10,17 @@ import Foundation class Tool: Entity { let id: EntityId private let position: CGPoint - - init(id: EntityId, position: CGPoint) { + private let sprite: String + private let size: CGSize + init(id: EntityId, position: CGPoint, sprite: String, size: CGSize) { self.id = id self.position = position + self.sprite = sprite + self.size = size } - convenience init(position: CGPoint) { - self.init(id: UUID(), position: position) + convenience init(position: CGPoint, sprite: String, size: CGSize) { + self.init(id: UUID(), position: position, sprite: sprite, size: size) } func setUpAndAdd(to: EntityManager) { diff --git a/star-dash/star-dash/Model/Level.swift b/star-dash/star-dash/Model/Level.swift new file mode 100644 index 00000000..db8e47de --- /dev/null +++ b/star-dash/star-dash/Model/Level.swift @@ -0,0 +1,21 @@ +// +// Level.swift +// star-dash +// +// Created by Lau Rui han on 21/3/24. +// + +import Foundation +struct Level { + var name: String + var entities: [Entity] + init(name: String, entities: [Entity]) { + self.name = name + self.entities = entities + } + + init(levelPersistable: LevelPersistable, entityPersistables: [EntityPersistable]) { + let entities = entityPersistables.compactMap { $0.toEntity() } + self.init(name: levelPersistable.name, entities: entities) + } +} diff --git a/star-dash/star-dash/Persistence/CollectibleEntityPersistable.swift b/star-dash/star-dash/Persistence/CollectibleEntityPersistable.swift new file mode 100644 index 00000000..f5e4d63b --- /dev/null +++ b/star-dash/star-dash/Persistence/CollectibleEntityPersistable.swift @@ -0,0 +1,19 @@ +// +// CollectibleEntityPersistable.swift +// star-dash +// +// Created by Lau Rui han on 22/3/24. +// + +import Foundation +struct CollectibleEntityPersistable: Codable, EntityPersistable { + + var levelId: Int64 + var position: CGPoint + var sprite: String + var points: Int + var size: CGSize + func toEntity() -> Entity { + Collectible(position: self.position, sprite: self.sprite, points: self.points, size: self.size) + } +} diff --git a/star-dash/star-dash/Persistence/Data/LevelData.swift b/star-dash/star-dash/Persistence/Data/LevelData.swift new file mode 100644 index 00000000..2cfaf90a --- /dev/null +++ b/star-dash/star-dash/Persistence/Data/LevelData.swift @@ -0,0 +1,18 @@ +// +// LevelData.swift +// star-dash +// +// Created by Lau Rui han on 22/3/24. +// + +import Foundation + +struct LevelData: Codable { + var id: Int64 + var name: String + var size: CGSize + var monsters: [MonsterEntityPersistable] + var obstacles: [ObstacleEntityPersistable] + var collectibles: [CollectibleEntityPersistable] + var tools: [ToolEntityPersistable] +} diff --git a/star-dash/star-dash/Persistence/Data/data.json b/star-dash/star-dash/Persistence/Data/data.json new file mode 100644 index 00000000..1d27e975 --- /dev/null +++ b/star-dash/star-dash/Persistence/Data/data.json @@ -0,0 +1,41 @@ +{ +"id": 0, + "name": "1", + "size": [1000, 200], + "obstacles": [ + { + "levelId": 0, + "sprite": "", + "position": [100, 200], + "size": [10,10] + }, + + ], + "collectibles": [ + { + "levelId": 0, + "sprite": "", + "position": [200, 200], + "size": [10,10], + "points": 10 + } + ], + + "tools": [ + { + "levelId": 0, + "sprite": "", + "position": [300, 200], + "size": [10,10] + }, + ], + "monsters": [ + { + "levelId": 0, + "sprite": "", + "position": [400, 100], + "size": [10,10], + "health": 100 + }, + ] +} diff --git a/star-dash/star-dash/Persistence/Database.swift b/star-dash/star-dash/Persistence/Database.swift new file mode 100644 index 00000000..b366a869 --- /dev/null +++ b/star-dash/star-dash/Persistence/Database.swift @@ -0,0 +1,324 @@ +// +// AppDatabase.swift +// star-dash +// +// Created by Lau Rui han on 20/3/24. +// + +import Foundation +import os.log +import SQLite + +struct Database { + static let DIR_DB = "StarDashDB" + static let DB_NAME = "stardash.sqlite3" + private let levelTable = Table("level") + private let collectibleTable = Table("collectible") + private let obstacleTable = Table("obstacle") + private let toolTable = Table("tool") + private let monsterTable = Table("monster") + private var db: Connection? + // Define a dictionary to map entity types to tables + let tableMap: [ObjectIdentifier: Table] + init() { + tableMap = [ + ObjectIdentifier(CollectibleEntityPersistable.self): self.collectibleTable, + ObjectIdentifier(ToolEntityPersistable.self): self.toolTable, + ObjectIdentifier(ObstacleEntityPersistable.self): self.obstacleTable, + ObjectIdentifier(MonsterEntityPersistable.self): self.monsterTable + ] + + if let docDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first { + let dirPath = docDir.appendingPathComponent(Self.DIR_DB) + + do { + try FileManager.default.createDirectory( + atPath: dirPath.path, + withIntermediateDirectories: true, + attributes: nil) + let dbPath = dirPath.appendingPathComponent(Self.DB_NAME).path + self.db = try Connection(dbPath) + dropAllTables() + createAllTables() + insertJsonData() + print("SQLiteDataStore init successfully at: \(dbPath) ") + } catch { + db = nil + print("SQLiteDataStore init error: \(error)") + } + } else { + db = nil + } + } + + private func dropAllTables() { + guard let db = db else { + return + } + do { + try db.run(levelTable.drop()) + try db.run(obstacleTable.drop()) + try db.run(collectibleTable.drop()) + try db.run(toolTable.drop()) + try db.run(monsterTable.drop()) + } catch { + print("Error deleting table \(error)") + } + + } + + private func createAllTables() { + createLevelTable() + createCollectibleTable() + createToolTable() + createMonsterTable() + createObstacleTable() + } + + private func createLevelTable() { + guard let db = db else { + return + } + let id = Expression("id") + let name = Expression("name") + let size = Expression("size") + do { + try db.run( levelTable.create { table in + table.column(id, primaryKey: true) + table.column(name) + table.column(size) + }) + print("Level table created") + } catch { + print("Error creating table \(error)") + } + } + + private func createCollectibleTable() { + guard let db = db else { + return + } + let id = Expression("id") + let levelId = Expression("levelId") + let position = Expression("position") + let sprite = Expression("sprite") + let points = Expression("points") + let size = Expression("size") + + do { + try db.run( collectibleTable.create { table in + table.column(id, primaryKey: .autoincrement) + table.column(levelId) + table.column(position) + table.column(sprite) + table.column(points) + table.column(size) + + }) + print("Collectible table created") + } catch { + print("Error creating table \(error)") + } + } + + private func createObstacleTable() { + guard let db = db else { + return + } + let id = Expression("id") + let levelId = Expression("levelId") + let position = Expression("position") + let sprite = Expression("sprite") + let size = Expression("size") + + do { + try db.run( obstacleTable.create { table in + table.column(id, primaryKey: .autoincrement) + table.column(levelId) + table.column(position) + table.column(sprite) + table.column(size) + + }) + print("Obstacle table created") + } catch { + print("Error creating table \(error)") + } + } + + private func createToolTable() { + guard let db = db else { + return + } + let id = Expression("id") + let levelId = Expression("levelId") + let position = Expression("position") + let sprite = Expression("sprite") + let size = Expression("size") + + do { + try db.run( toolTable.create { table in + table.column(id, primaryKey: .autoincrement) + table.column(levelId) + table.column(position) + table.column(sprite) + table.column(size) + + }) + print("Tool table created") + } catch { + print("Error creating table \(error)") + } + } + private func createMonsterTable() { + guard let db = db else { + return + } + let id = Expression("id") + let levelId = Expression("levelId") + let position = Expression("position") + let sprite = Expression("sprite") + let size = Expression("size") + let health = Expression("health") + + do { + try db.run( monsterTable.create { table in + table.column(id, primaryKey: .autoincrement) + table.column(levelId) + table.column(position) + table.column(sprite) + table.column(size) + table.column(health) + + }) + print("Monster table created") + } catch { + print("Error creating table \(error)") + } + } + + func insert(persistable: LevelPersistable) { + guard let database = db else { + return + } + + do { + let insert = try self.levelTable.insert(persistable) + try database.run(insert) + } catch { + print("Error saving level \(error)") + } + } + + func insert(persistable: T) { + guard let database = db else { + return + } + + do { + // Get the corresponding table for the type + guard let table = self.tableMap[ObjectIdentifier(T.self)] else { + print("Unsupported entity type") + return + } + + let insert = try table.insert(persistable) + try database.run(insert) + } catch { + print("Error saving level \(error)") + } + } + +} + +extension Database { + func insertJsonData() { + do { + if let fileURL = Bundle.main.url(forResource: "data", withExtension: "json") { + // Read JSON data from the file + do { + let jsonData = try Data(contentsOf: fileURL) + // Decode JSON data into LevelData + let levelData = try JSONDecoder().decode(LevelData.self, from: jsonData) + let levelPersistable = LevelPersistable(id: levelData.id, name: levelData.name, size: levelData.size) + insert(persistable: levelPersistable) + for persistable in levelData.collectibles { + insert(persistable: persistable) + } + for persistable in levelData.tools { + insert(persistable: persistable) + } + for persistable in levelData.obstacles { + insert(persistable: persistable) + } + for persistable in levelData.monsters { + insert(persistable: persistable) + } + + } catch { + print("Error reading or decoding JSON: \(error)") + } + } else { + print("JSON file not found.") + } + } catch { + print(error) + } + } + + func getLevelPersistable(id: Int64) -> LevelPersistable? { + guard let database = db else { + return nil + } + let idColumn = Expression("id") + do { + let loadedLevel: [LevelPersistable] = + try database.prepare(levelTable.filter(id == idColumn)).map { row in + let persistable: LevelPersistable = try row.decode() + return persistable + + } + if loadedLevel.isEmpty { + return nil + } + return loadedLevel[0] + + } catch { + print("Error fetching levels \(error)") + return nil + } + } + + func getAllEntities(levelId: Int64) -> [EntityPersistable] { + guard let database = db else { + return [] + } + var entities: [EntityPersistable] = [] + let levelIdColumn = Expression("levelId") + do { + entities += try database.prepare(collectibleTable.filter(levelId == levelIdColumn)).map { row in + let persistable: CollectibleEntityPersistable = try row.decode() + return persistable + } + entities += try database.prepare(toolTable.filter(levelId == levelIdColumn)).map { row in + let persistable: ToolEntityPersistable = try row.decode() + return persistable + } + entities += try database.prepare(obstacleTable.filter(levelId == levelIdColumn)).map { row in + let persistable: ObstacleEntityPersistable = try row.decode() + return persistable + } + entities += try database.prepare(monsterTable.filter(levelId == levelIdColumn)).map { row in + let persistable: MonsterEntityPersistable = try row.decode() + return persistable + } + } catch { + print("Error retriving \(error)") + + } + + return entities + + } + +} diff --git a/star-dash/star-dash/Persistence/EntityPersistable.swift b/star-dash/star-dash/Persistence/EntityPersistable.swift new file mode 100644 index 00000000..2d564b3f --- /dev/null +++ b/star-dash/star-dash/Persistence/EntityPersistable.swift @@ -0,0 +1,12 @@ +// +// EntityPersistable.swift +// star-dash +// +// Created by Lau Rui han on 21/3/24. +// + +import Foundation + +protocol EntityPersistable: Codable { + func toEntity() -> Entity +} diff --git a/star-dash/star-dash/Persistence/LevelPersistable.swift b/star-dash/star-dash/Persistence/LevelPersistable.swift new file mode 100644 index 00000000..ff629912 --- /dev/null +++ b/star-dash/star-dash/Persistence/LevelPersistable.swift @@ -0,0 +1,14 @@ +// +// LevelPersistable.swift +// star-dash +// +// Created by Lau Rui han on 21/3/24. +// + +import Foundation +struct LevelPersistable: Codable { + var id: Int64 + var name: String + var size: CGSize + +} diff --git a/star-dash/star-dash/Persistence/MonsterEntityPersistable.swift b/star-dash/star-dash/Persistence/MonsterEntityPersistable.swift new file mode 100644 index 00000000..7eb012ab --- /dev/null +++ b/star-dash/star-dash/Persistence/MonsterEntityPersistable.swift @@ -0,0 +1,21 @@ +// +// MonsterEntityPersistable.swift +// star-dash +// +// Created by Lau Rui han on 22/3/24. +// + +import Foundation + +struct MonsterEntityPersistable: Codable, EntityPersistable { + + var levelId: Int64 + var position: CGPoint + var sprite: String + var health: Int + var size: CGSize + + func toEntity() -> Entity { + Monster(position: self.position, health: self.health, sprite: self.sprite, size: self.size) + } +} diff --git a/star-dash/star-dash/Persistence/ObstacleEntityPersistable.swift b/star-dash/star-dash/Persistence/ObstacleEntityPersistable.swift new file mode 100644 index 00000000..e2e0d723 --- /dev/null +++ b/star-dash/star-dash/Persistence/ObstacleEntityPersistable.swift @@ -0,0 +1,18 @@ +// +// ObstacleEntityPersistable.swift +// star-dash +// +// Created by Lau Rui han on 22/3/24. +// + +import Foundation +struct ObstacleEntityPersistable: Codable, EntityPersistable { + + var levelId: Int64 + var position: CGPoint + var sprite: String + var size: CGSize + func toEntity() -> Entity { + Obstacle(position: self.position, sprite: self.sprite, size: self.size) + } +} diff --git a/star-dash/star-dash/Persistence/StorageManager.swift b/star-dash/star-dash/Persistence/StorageManager.swift new file mode 100644 index 00000000..f60b5e24 --- /dev/null +++ b/star-dash/star-dash/Persistence/StorageManager.swift @@ -0,0 +1,24 @@ +// +// StorageManager.swift +// star-dash +// +// Created by Lau Rui han on 20/3/24. +// + +import Foundation + +class StorageManager { + let database: Database + + init() { + database = Database() + } + + func getLevel(id: Int64) -> Level? { + if let levelPersistable = self.database.getLevelPersistable(id: id) { + let entityPersistables = self.database.getAllEntities(levelId: id) + return Level(levelPersistable: levelPersistable, entityPersistables: entityPersistables) + } + return nil + } +} diff --git a/star-dash/star-dash/Persistence/ToolEntityPersistable.swift b/star-dash/star-dash/Persistence/ToolEntityPersistable.swift new file mode 100644 index 00000000..5b41bccb --- /dev/null +++ b/star-dash/star-dash/Persistence/ToolEntityPersistable.swift @@ -0,0 +1,19 @@ +// +// ToolEntityPersistable.swift +// star-dash +// +// Created by Lau Rui han on 22/3/24. +// + +import Foundation +struct ToolEntityPersistable: Codable, EntityPersistable { + + var levelId: Int64 + var position: CGPoint + var sprite: String + var size: CGSize + + func toEntity() -> Entity { + Tool(position: self.position, sprite: self.sprite, size: self.size) + } +} diff --git a/star-dash/star-dash/ViewController.swift b/star-dash/star-dash/ViewController.swift index e7bc563c..f1ce2a83 100644 --- a/star-dash/star-dash/ViewController.swift +++ b/star-dash/star-dash/ViewController.swift @@ -14,7 +14,7 @@ class ViewController: UIViewController { var renderer: Renderer? var gameBridge: GameBridge? var gameEngine: GameEngine? - + var storageManager: StorageManager? override func viewDidLoad() { super.viewDidLoad() @@ -25,7 +25,7 @@ class ViewController: UIViewController { let gameEngine = GameEngine() self.gameEngine = gameEngine self.gameBridge = GameBridge(entityManager: gameEngine, scene: scene) - + self.storageManager = StorageManager() setupGameEntities() guard let renderer = MTKRenderer(scene: scene) else { @@ -58,6 +58,14 @@ class ViewController: UIViewController { let floor = Floor(position: CGPoint(x: scene.size.width / 2, y: scene.size.height / 2 - 400)) floor.setUpAndAdd(to: entityManager) + if let level = self.storageManager?.getLevel(id: 0) { + for entity in level.entities { + entity.setUpAndAdd(to: entityManager) + } + } else { + print("level not found") + } + let collectible = Collectible.createCoinCollectible( position: CGPoint(x: scene.size.width / 2 + 30, y: scene.size.height / 2 - 100) )