Reputation: 1505
I'm making a Swift game with a lot of levels. Each level is in a file with sequential names:
Level1.swift
Level2.swift
Level3.swift
...
And each file has a single class in it, with the class name being the same as the file name.
The reason everything is a class is because all the classes share similar functions from a base class, and also because of lots of shared variables.
The problem is, I have no idea how to load the level by just using an integer without doing this sort of disaster:
switch Int(level)! {
case 1:
newScene = Level1(size: frame.size)
case 2:
newScene = Level2(size: frame.size)
case 3:
newScene = Level3(size: frame.size)
...
I can't use SKScene(fileNamed:)
because I don't have any .sks files, only the actual classes. It'd be great if I could simply load a scene as I can with audio files - by using Bundle.main.url - but this isn't possible. I tried using a protocol with extensions for default behaviours, but I still can't think of a way to load it nicely. Any help would be appreciated.
Upvotes: 4
Views: 159
Reputation: 6824
You will need to obtain classString for your Classes, luckily your classes present a common pattern, so this will be easy for you. Here's an snippet with this code for you to test
import Foundation
class Level {
required init() {}
}
class Level1 : Level {
required init() {
print("Created Level 1")
}
}
class Level2 : Level {
required init() {
print("Created Level 2")
}
}
var myLevelClassRealName = NSStringFromClass(Level.self)
print("This String is what we need : \(myLevelClassRealName)")
var level = 2
print("The Level I want to load is Level # \(level)")
myLevelClassRealName = myLevelClassRealName + "\(level)"
print("Real name for my class is : " + myLevelClassRealName)
let myLookupClass = NSClassFromString(myLevelClassRealName) as! Level.Type
let currentLevel = myLookupClass.init()
print("Created a class of type : \(type(of: currentLevel))")
The output for this in my case is:
This String is what we need : TempCode.Level
The Level I want to load is Level # 2
Real name for my class is : TempCode.Level2
Created Level 2
Created a class of type : Level2
Upvotes: 1
Reputation: 93191
I think that your "disastrous" solution does offer the cleanest, type-checked solution in Swift. If you want to initialize a class based on a name known only at run time, you have to venture into ObjC territory:
First, make the base class inherits from NSObject
(if it doesn't already):
class Level : NSObject {
var levelId: Int = 0
required init(size: NSSize) { }
// all other common stuffs here
}
class Level1 : Level {
required init(size: NSSize) {
super.init(size: size)
self.levelId = 1
// ...
}
}
class Level2 : Level {
required init(size: NSSize) {
super.init(size: size)
self.levelId = 2
// ...
}
}
class Level3 : Level {
required init(size: NSSize) {
super.init(size: size)
self.levelId = 3
// ...
}
}
Here's how to use it:
let levelId = 2
// Module Name = Product Name from your Build Settings, unless you put
// the Level classes into sub-namespace
let className = "ModuleName.Level\(levelId)"
let LevelClass = NSClassFromString(className) as! Level.Type
// At build time, the compiler sees this as an object of type Level,
// but it's actually a Level2 object. You can cast it to Level2 if
// you need to do something specific to that level.
let level = LevelClass.init(size: frame.size)
Upvotes: 2