Lukas
Lukas

Reputation: 491

Use a Variable in a different Scene

In my GameScene I have a variable called "score" which I want to use in the MenuScene to display the latest score. I have tried it with the UserDefaults but I didn't really understand it and it didn't work. Could someone tell me how I have to declare the score variable so I can use it in other scenes, and it would also be nice if someone could show me a way how to save a variable so I can save the highscore.

I've tried this:

GameScene:

let userDefaults = UserDefaults.standard // Before didMove func
UserDefaults.setValue(score, forKey: "Score") // in gameOver func

MenuScene:

var score = SKLabelNode()
var scoreNumber = UserDefaults.standard.integer(forKey: "Score")

If I run the Game it loads the MenuScene and I can press on play and it goes to the GameScene. If I die in the GameScene, the game crashes and i get this error: "Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key Score.'"

Upvotes: 0

Views: 490

Answers (4)

tanz
tanz

Reputation: 2547

If you use user defaults correctly you should be able to read and write from any object as UserDefaults.standard is a singleton. Something along these lines should work:

private let scoreUserDefaultsKey = "scoreUserDefaultsKey"

func save(score: Int)
{
    let userDefaults = UserDefaults.standard
    userDefaults.set(score, forKey: scoreUserDefaultsKey)
    userDefaults.synchronize()
}

func read() -> Int
{
    return UserDefaults.standard.integer(forKey: scoreUserDefaultsKey)
}

class GameScene1: SKScene {

    func saveFromGameScene1(score: Int) {
        save(score: score)
    }
}

class GameScene2: SKScene {

    func readFromGameScene2() -> Int {
        return read()
    }
}

Said that you could have a much nicer architecture implementing a ScoreManager object and inject it (instead of having hard dependencies on a singleton) into your game scenes.

UPDATE: Just seen your code snippet. You need to use set, not setValue. setValue is not a method of UserDefaults but is a method of NSObject for KVO.

func setValue(_ value: Any?, forKey key: String)

Sets the property of the receiver specified by a given key to a given value. If key identifies a to-one relationship, relate the object specified by value to the receiver, unrelating the previously related object if there was one. Given a collection object and a key that identifies a to-many relationship, relate the objects contained in the collection to the receiver, unrelating previously related objects if there were any.

The search pattern that setValue:forKey: uses is described in Accessor Search Patterns in Key-Value Coding Programming Guide.

In a reference-counted environment, if the instance variable is accessed directly, value is retained.

That is not what you're looking for ;)

Also after you call set make sure to call synchronize as well to be certain that the value is stored immediately.

I hope this helps.

Upvotes: 1

sicvayne
sicvayne

Reputation: 620

You could just move your var gameScore from being global, meaning removing it from GameScene , and make it universal by moving it in between import SpriteKit and class GameScene. After that, you could just use NSUserDefault just as the others have suggested.

UPDATE

ok so for my GameOver scenes, i usually do something like this within the class

let defaults = NSUserDefaults()
var highScoreNumber = defaults.integerForKey("highScoreSaved")

If gameScore > highScoreNumber{
highScoreNumber = gameScore
defaults.setInteger(highScoreNumber, forKey: "highScoreSaved")
  }

You can just do this in your MenuScene instead.

Upvotes: 0

Abel Lee
Abel Lee

Reputation: 55

I don't think you should use UserDefaults to do this,

first reason that I wanna ask you, if someone close the app, maybe they kill the app when they are playing, how you make the deal with the score?

Definitely, you should not keep this score, right? or you should cache all data of the game, then when the player goes into the game again, you could back to the state when the app was closed. But I think it is so complex to do this.

So I think you should put this score globally or easier to control this is just pass the score from the GameScene to the maybe called ResultScene.

I do suppose your game have these scenes: LoginScene MapScene GameScene ResultScene RankScene

so you just use the score in the GameScene and ResultScene in real time, so you just do this in the GameScene:

var score   : Int/CGFloat     = 0;
func enterFrame(){
   //if they got the goal, then
   // score = score + 1 or much more;
}

func gameOver(){
   let resultScene = ResultScene(score: score);
   // or you could just create the empty ResultScene, and pass the score via a public func of the ResultScene
}

then you could show the score in the ResultScene.

if you are just making a server-less game, you should keep the scores into the UserDefaults or CoreData or SQLite etc. then you can show the scores in some scenes about the history records.

Upvotes: 0

J. Sue
J. Sue

Reputation: 79

If you want a dynamic variable in difference viewcontroller. And use it all the time. You make create in Appdelegate.

In your AppDelegate.swift

    var yourVariableName: Any = "Value"

When you wanna use it or change it. You

    let app: AppDelegate = UIApplication.shared.delegate as! AppDelegate
    print(app.youVariableName)

If you wanna observe the variable value change and do some Action. You make setting up a didSet function.

var yourVariableName: Any = "VALUE" {didSet{observeValueChangeFunction()}}
func didSet{observeValueChangeFunction() {
     print("New value: \(yourVariableName)")
}

Upvotes: 0

Related Questions