Search code examples
iosswiftsprite-kitnsuserdefaultsskscene

Passing a string between SKScenes using UserDefaults


In a game I'm developing with Swift, I want the player to be able to choose a background in a shop-like scene and thus change the background of every SKScene. I'm trying to accomplish this using UserDefaults, but for some reason it isn't working. Here's the important code of the Shop Scene (I removed the irrelevant code):

import SpriteKit

class ShopScene: SKScene {

var backNumber = 90
var backRemainder = 0
var background = SKSpriteNode()
var backName:String = "back1"

override func didMove(to view: SKView) {

    background.texture = SKTexture(imageNamed: "\(backName)")
    background.size = self.size
    self.addChild(background)

    let nextButton: NButton = NButton(defaultButtonImage: "next", activeButtonImage: "nextP", buttonAction: nextAction)
    addChild(nextButton)

    let selectButton: SButton = SButton(defaultButtonImage: "next", activeButtonImage: "nextP", buttonAction: selectAction)
    addChild(selectButton)   
}

func selectAction() {

    UserDefaults.standard.set(backName, forKey: "backSaved")

    let sceneToMoveTo = MainMenuScene(size: self.size)
    sceneToMoveTo.scaleMode = self.scaleMode
    let sceneTransition = SKTransition.fade(withDuration: 0.4)
    self.view!.presentScene(sceneToMoveTo, transition: sceneTransition)   
}

func nextAction() {

    backNumber += 1
    backRemainder = backNumber % 3

    switch backRemainder {
    case 0:
        backName = "back1"
    case 1:
        backName = "back2"
    case 2:
        backName = "back3"
    default:
        backName = "back1"
    }

    background.texture = SKTexture(imageNamed: "\(backName)")
}
}

As you can see, when the select button is pressed backName is saved. Now, this is the relevant code of the Main Menu Scene:

Import SpriteKit

class MainMenuScene: SKScene {

var backName = UserDefaults.standard.string(forKey: "backSaved")

override func didMove(to view: SKView) {

    let background = SKSpriteNode(imageNamed: "\(backName)")
    background.size = self.size
    self.addChild(background)

When the 'Select' button is pressed, you should transition to Main Menu Scene and see that the background is the one you selected. However, I get a red X in a white background when I run this. I've worked before with UserDefaults to save scores, but I can't figure out why it's not working in this case. Any idea on how to pass strings between SKScenes using UserDefaults? Am I doing something wrong?


NOTE: Although Knight0fDragon's answer is marked correct, his answer works for passing a string between scenes but not for saving that value permanently. In you wish to pass the value and save it, check my answer.


Solution

  • I would recommend not doing UserDefaults, that is meant for preferences to your application. Instead use userData

    func selectAction() {
    
        let sceneToMoveTo = MainMenuScene(size: self.size)
        sceneToMoveTo.scaleMode = self.scaleMode
        sceneToMoveTo.userData = sceneToMoveTo.userData ?? NSMutableDictionary() //This lets us ensure userdata exists
        sceneToMoveTo.userData!["backSaved"] = backName
        let sceneTransition = SKTransition.fade(withDuration: 0.4)
        self.view!.presentScene(sceneToMoveTo, transition: sceneTransition)   
    }
    

    Then implement it with:

    import SpriteKit
    
    class MainMenuScene: SKScene {
    
    
    
    lazy var backName:String = {return self.userData?["backSaved"] as? String ?? "back1"}() //This allows us to load backName at the time it is needed, or assign back1 if doesn't exist
    
    override func didMove(to view: SKView) {
    
    
        let background = SKSpriteNode(imageNamed: backName)
        self.addChild(background)
    

    Note, I am not sure if the as? String is needed, try it without it and see if Swift can infer it