Joyful Games – iPhone game development with Apple’s Swift new language

Email Contact

Play SnakeJoy! The most addicting fun snake game on the internet

Play SnakeJoy in Your Browser!

Saving SpriteKit Game Data in Swift – It’s Easy with NSCoder

Saving SpriteKit game information on iOS is a common requirement. We need to save the player’s progress, preferences, and high scores. I recently implemented a high score system for Krate. Though this Swift code is tailored for score keeping, it can be easily adapted to persist save game data or any other information you need to save between games.

We’ll inherit NSCoder to serialize our objects, then store the data using one of several iOS data persistence methods. I chose to save my high score data using plist files, saved inside my game’s directory. (You can also use NSUserDefaults to store serialized objects, though it is intended only for user preferences. Another option is Core Data, which is robust and useful for complicated Swift persistence tasks.)

First off, we’ll need a serializable class that will store our save game data. NSCoding allows us to translate our class into data that can be saved to the file system.

// inherit from NSCoding to make instances serializable
class HighScore: NSObject, NSCoding {
    let score:Int;
    let dateOfScore:NSDate;
    
    init(score:Int, dateOfScore:NSDate) {
        self.score = score;
        self.dateOfScore = dateOfScore;
    }
    
    required init(coder: NSCoder) {
        self.score = coder.decodeObjectForKey("score")! as Int;
        self.dateOfScore = coder.decodeObjectForKey("dateOfScore")! as NSDate;
        super.init()
    }
    
    func encodeWithCoder(coder: NSCoder) {
        coder.encodeObject(self.score, forKey: "score")
        coder.encodeObject(self.dateOfScore, forKey: "dateOfScore")
    }
}

Great! Next, we want to create a manager class to store an array of HighScores, and perform the save action. Mind the comments – the code required to save and read plist files is long:

class HighScoreManager {
    var scores:Array<HighScore> = [];
    
    init() {
        // load existing high scores or set up an empty array
        let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
        let documentsDirectory = paths[0] as String
        let path = documentsDirectory.stringByAppendingPathComponent("HighScores.plist")
        let fileManager = NSFileManager.defaultManager()
        
        // check if file exists
        if !fileManager.fileExistsAtPath(path) {
            // create an empty file if it doesn't exist
            if let bundle = NSBundle.mainBundle().pathForResource("DefaultFile", ofType: "plist") {
                fileManager.copyItemAtPath(bundle, toPath: path, error:nil)
            }
        }
        
        if let rawData = NSData(contentsOfFile: path) {
            // do we get serialized data back from the attempted path?
            // if so, unarchive it into an AnyObject, and then convert to an array of HighScores, if possible
            var scoreArray: AnyObject? = NSKeyedUnarchiver.unarchiveObjectWithData(rawData);
            self.scores = scoreArray as? [HighScore] ?? [];
        }
    }
    
    func save() {
        // find the save directory our app has permission to use, and save the serialized version of self.scores - the HighScores array.
        let saveData = NSKeyedArchiver.archivedDataWithRootObject(self.scores);
        let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray;
        let documentsDirectory = paths.objectAtIndex(0) as NSString;
        let path = documentsDirectory.stringByAppendingPathComponent("HighScores.plist");
        
        saveData.writeToFile(path, atomically: true);
    }
    
    // a simple function to add a new high score, to be called from your game logic
    // note that this doesn't sort or filter the scores in any way
    func addNewScore(newScore:Int) {
        let newHighScore = HighScore(score: newScore, dateOfScore: NSDate());
        self.scores.append(newHighScore);
        self.save();
    }
}

We can persist any type of game data we need with this same pattern . . . and though the code to write and read plist files is a bit verbose, the concept is actually quite straightforward. Kindly leave a comment with any questions or suggestions.

A quick note of credit: thanks to this blog post for solid plist persistence info.