0

How would I save the current level that the user is on. I have tried using the fallowing code but it does not work. When the user leaves the game I want the game to pick back up on the level they left off at.

This is in my viewcontroller.swift file:

import UIKit
import SpriteKit
import GameplayKit

class GameViewController: UIViewController {

override func viewDidLoad() {
    super.viewDidLoad()

    if let view = self.view as! SKView? {

        guard let scene = GameScene.loadScene() ?? SKScene(fileNamed: "mainMenu") as? GameScene else {
            fatalError("Scene not loaded")

        }
        scene.scaleMode = .aspectFill

        view.presentScene(scene)
        view.ignoresSiblingOrder = true

        view.showsFPS = true
        view.showsNodeCount = true
    }
}

override var shouldAutorotate: Bool {
    return true
}

override var supportedInterfaceOrientations: UIInterfaceOrientationMask 
{
    if UIDevice.current.userInterfaceIdiom == .phone {
        return .allButUpsideDown
    } else {
        return .all
    }
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Release any cached data, images, etc that aren't in use.
}

override var prefersStatusBarHidden: Bool {
    return true
}
}

I have a mainMenu screen and when the user taps on the screen I want that person to be presented with the most recent level that the user was on.

This is in my mainMenu.swift file:

import SpriteKit
import GameplayKit

var welcomeLabel = SKLabelNode()
var playLabel = SKLabelNode()

class GameScene: SKScene {
required init?(coder decoder: NSCoder) {
    super.init(coder: decoder)
    decoder.decodeBool(forKey: "savingKey")

    NotificationCenter.default.addObserver(self, selector: 
#selector(applicationDidEnterBackground), name: 
.UIApplicationDidEnterBackground, object: nil)
}


func applicationDidEnterBackground() {
    // Save Scene
    let sceneData = NSKeyedArchiver.archivedData(withRootObject: self)
    UserDefaults.standard.set(sceneData, forKey: "currentScene")
}

class func loadScene() -> SKScene? {
    var scene: GameScene?

    if let savedSceneData = UserDefaults.standard.object(forKey: 
"currentScene") as? NSData,
        let savedScene = NSKeyedUnarchiver.unarchiveObject(with: 
savedSceneData as Data) as? GameScene {
        scene = savedScene
    } else {
        scene = nil
    }

    return scene
}

override func didMove(to view: SKView) {

    welcomeLabel = SKLabelNode(fontNamed: "Courier New")
    welcomeLabel.text = "Go round Go"
    welcomeLabel.fontColor = SKColor.green
    welcomeLabel.fontSize = 50
    welcomeLabel.position = CGPoint(x: frame.midX, y: 500)
    addChild(welcomeLabel)

    backgroundColor = UIColor.lightGray

    playLabel = SKLabelNode(fontNamed: "Courier New")
    playLabel.text = "Tap anywhere to play"
    playLabel.fontColor = SKColor.green
    playLabel.fontSize = 50
    playLabel.position = CGPoint(x: frame.midX, y: frame.midY)
    addChild(playLabel)
}



override func touchesBegan(_ touches: Set<UITouch>, with event: 
UIEvent?) {

    welcomeLabel.isHidden = true
    playLabel.isHidden = true
    let nextlevel = levelOne(fileNamed: "levelOne")
    nextlevel?.scaleMode = .aspectFill
    self.view?.presentScene(nextlevel!, transition: 
SKTransition.fade(withDuration: 0.1))
    }


override func update(_ currentTime: TimeInterval) {

}
}

I would like if someone can explain to me how to do this. I am not experienced when it comes to encoding and decoding.

iGatiTech
  • 2,306
  • 1
  • 21
  • 45

1 Answers1

0

You don't need to use encoding and decoding here. You can just save the name of the current scene in the user defaults. You don't even need the notification center. Just save the name of the scene in the user defaults like this:

class GameScene:SKScene {

    let defaults = UserDefaults.standard

    override func didMove(to view: SKView) {

        defaults.set(currentLevel, forKey: "continuePoint")

    }

}

Now in your GameViewController class check if there is a saved continuePoint. If this is the case, load the scene. If this is not the case, load your initial level:

class GameViewController: UIViewController{

    let defaults = UserDefaults.standard

    override func viewDidLoad(){
        super.viewDidLoad()

        //Switch this with the name of your first level.
        var initialLevel = "initialLevelName"

        if defaults.object(forKey: "continuePoint") != nil{
            initialLevel = defaults.string(forKey: "continuePoint")!
        }

        if let view = self.view as! SKView? {

            if let scene = GameScene(fileNamed: initialLevel) {

                scene.scaleMode = .aspectFill
                scene.currentLevel = initialLevel

                view.presentScene(scene)
            }
        }
    }

}

Unfortunately I can't test this at the moment, but something like this will definitely work, I use this in my game too.

EDIT:

I looked at your project and I see that you change your level in your mainMenu.swift to your firstLevel everytime. You can do something like this in your touchesBegan:

    var levelName = ""
    var nextLevel: SKScene?

    if defaults.object(forKey: "continuePoint") != nil && defaults.string(forKey: "continuePoint") != ""{
        levelName = defaults.string(forKey: "continuePoint")!
    }
    else{
        levelName = "levelOne"
    }      
    if levelName == "levelOne"{
        nextLevel = levelOne(fileNamed: levelName)
    }
    else if levelName == "levelTwo"{
        nextLevel = levelTwo(fileNamed: levelName)
    }
    nextLevel?.scaleMode = .aspectFill
    self.view?.presentScene(nextLevel!, transition: SKTransition.fade(withDuration: 0.1))

    }

But this is not the best way to do it. The problem is that you have a new subclass of SKScene for every level that is in the game. If you use a .plist file and set the properties for the specific levels there, you can parse it in your GameScene class and then you have only one SKScene class for all of your Scenes. Your Menu Scene should be in a seperate class though.

You sould find out how to use a plist file in your game to have a better structure. And btw: It is good to use source control like github or bitbucket in all of your projects. So when your project gets bigger and you build in new features and your code doesn't work as expected, you can easily set it back to an older state.

Marcel
  • 472
  • 2
  • 6
  • 19
  • the code works fine and i get no errors, but when i build and run the application i get Thread1: signal CIGABRT error i tested the code and its the defaults.set(levelOne(), forKey: "continuePoint") line of code that makes this error occur. – Matt Young Jan 26 '18 at 00:31
  • Look at the console for detailed information about the exception your Code is throwing and post it here. Btw: what does your levelOne() function do? – Marcel Jan 26 '18 at 00:53
  • like you said, i put the name of the scene in that space, xcode makes me put a "()" or ".self" after it or it will give me an error. That auto correct makes me do that. its not a function. – Matt Young Jan 26 '18 at 00:56
  • You need to save the name of the level in the UserDefaults as String, so try this: `defaults.set("levelOne", forKey: "continuePoint")` – Marcel Jan 26 '18 at 01:09
  • If you want to use exactly this code, you have to set up a variable in your GameScene which is called currentLevel. This variable must contain the name of the actual scene, for example "levelOne". Then just use this variable as mentioned above. – Marcel Jan 26 '18 at 01:19
  • Sorry my fault, I looked into my project and I see that the code works if you just declare the variable in your GameScene class like this: `var currentLevel = ""`. Then just set the `var initialLevel = "initialLevelName"` in your GameViewController class to the name of your first level. – Marcel Jan 26 '18 at 01:23
  • try what I mentioned above, if this doesn't work, set a print statement inside your `if defaults.object(forKey: "continuePoint") != nil{ }`. Is it called? Does the initialLevel change? – Marcel Jan 26 '18 at 01:28
  • yes it was called, i put print("hello") and the statement was called – Matt Young Jan 26 '18 at 01:52
  • Is the project on github? Then I will have a look at it and maybe find the issue – Marcel Jan 26 '18 at 02:04
  • gonna make an account now, will post the link when i post the project, dude i really appreciate your time. – Matt Young Jan 26 '18 at 02:06
  • I looked at your project and your problem is that you set your level the `touchesBegan()` of your mainMenu.swift file to first level everytime. I edited my answer. – Marcel Jan 26 '18 at 03:06
  • so would i put the initialLevel there or the currentLevel there? because thats how i transition from one level to the next – Matt Young Jan 26 '18 at 03:09
  • I forgot to mention that of course you have to set the user defaults in the didMove() methods of your levels like this: `defaults.set("levelTwo", forKey: "continuePoint")` or `defaults.set("levelThree", forKey: "continuePoint")`, etc. Because if you don't, you will always start at level 1. – Marcel Jan 26 '18 at 03:50
  • and in the viewController.swift do i keep everything you said in the first place? or disregard that. – Matt Young Jan 26 '18 at 04:20
  • thank you very much man!!!!!! I honestly really do appreciate your time, this did work for me!!! I can't thank you enough!!! I accepted your answer! – Matt Young Jan 26 '18 at 04:27
  • just realized why this is a bad idea, i will need to add an if statement for every level in my mainMenu.swift file – Matt Young Jan 26 '18 at 16:21
  • Yes, thats the problem. As I said you should consider using a property list for your game. Also you shouldn‘t make a new class for every level. – Marcel Jan 26 '18 at 18:13