MysteryPancake
MysteryPancake

Reputation: 1505

Swift: Load level class by integer

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

Answers (2)

Hugo Alonso
Hugo Alonso

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

Code Different
Code Different

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

Related Questions