Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
186 views
in Technique[技术] by (71.8m points)

ios - SpriteKit: Why does it wait one round for the score to update? (Swift)

In one scene, I have this code:

let defaults = NSUserDefaults.standardUserDefaults()
defaults.setInteger(score, forKey: "scoreKey")

defaults.synchronize()

When the user makes contact with the gap, the code runs:

 score++

If the user hits an obstacle, the GameOverScene takes over. Here's the code I have for the GameOverScene to move the score to scene to scene:

let defaults = NSUserDefaults.standardUserDefaults()
let score = defaults.integerForKey("scoreKey")
scoreLabel.text = "(score)"

However, there's a bug in my code where the scoreLabel doesn't update its text. For example, let's say a user scores 1 and dies. When he dies, the gameOverScene will come up and say that the score was 1. Then, lets say the user clicks restart, and scores 5 and then dies. In the GameOverScene, the scoreLabel will say 1.

Please help me!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You don't need to really call synchronise anymore if you use iOS 8 or above. This is recommended by Apple, yet a lot of people still do it. So get rid of that line if you still use it.

My preferred way for game data is using a singleton GameData class with NSCoding. No need to add variables all over the place and much cleaner. I advise you reading this.

http://www.raywenderlich.com/63235/how-to-save-your-game-data-tutorial-part-1-of-2

You can also integrate iCloud key value storage that way, it is very easy as its similar to user defaults (see my example below)

Anyway to start you off here is a simple example of how this could look.

import Foundation


/// Keys
private struct Key {
    static let encodedData = "encodedData"
    static let highScore = "highScore"
}

class GameData: NSObject, NSCoding {

// MARK: - Static Properties

/// Shared instance
static let shared: GameData = {
     if let decodedData = UserDefaults.standard.object(forKey: Key.encodedData) as? GameData {
        return gameData
    } else {
        print("No data, creating new")
        return GameData()
    }
}    

// MARK: - Properties

/// Defaults
private let localDefaults = UserDefaults.standard
private let iCloudDefaults = NSUbiquitousKeyValueStore.default()

/// Progress (not saved, no need for saving the score because of the highScore var. Still have here for global single access)
var score = 0

/// Progress (saved)
var highScore = 0

// MARK: - Init
private override init() {
    super.init()
    print("GameData init")
    NotificationCenter.default.addObserver(self, selector: #selector(updateFromCloud), name: NSUbiquitousKeyValueStore.didChangeExternallyNotification, object: iCloudDefaults)
    iCloudDefaults.synchronize()
}

// MARK: - Convenience Init
convenience required init?(coder decoder: NSCoder) {
    self.init()
    print("GameData convenience init")

    // Progress
    highScore = decoder.decodeInteger(forKey: Key.highScore)
}

// MARK: - Encode
func encodeWithCoder(encoder: NSCoder) {

    // Progress
    encoder.encodeInteger(highScore, forKey: Key.highScore)

// MARK: - User Methods

/// Save
func save() {

    if score > highScore {
        highScore = score
    }

    saveLocally()
    saveToCloud()
}

// MARK: - Internal Methods

/// Save locally
private func saveLocally() {
    let encodedData = NSKeyedArchiver.archivedDataWithRootObject(self)
    localDefaults.setObject(encodedData, forKey: Key.encodedData)
}

/// Save to icloud
private func saveToCloud() {
    print("Saving to iCloud")

    // Highscores
    if (highScore > iCloudDefaults.objectForKey(Key.highScore) as? Int ?? Int()) {
        iCloudDefaults.setObject(highScore, forKey: Key.highScore)
    }


/// Update from icloud
func updateFromCloud() {
    print("Updating from iCloud")

    // Highscores
    highScore = max(highScore, iCloudDefaults.object(forKey: Key.highScore) as? Int ?? Int())

    // Save
    saveLocally()
}

Now in any scene if you want to use the score or saved highScore property you for example could say

GameData.shared.score++

or

scoreLabel.text = "(GameData.shared.score)"
highScoreLabel.text = "(GameData.shared.highScore)"

All your text labels will be updated immediately if you transition to a new scene or update the .text property. No need for userDefault sync etc.

Calling ...shared... will also initialise the helper. If you want to load gameData as soon as your game has launched you could call

GameData.shared

in your appDelegate or viewController. Its probably not really needed but your could still do it just to ensure the helper is initialised as soon as the game is launched.

If you want to save you call

GameData.shared.save()

Just remember to reset the score back to 0 in your gameScene.swift in the ViewDidLoad method.

GameData.shared.score = 0

This should make your life much easier. If you want to use iCloud all you have to do is go to your target and capabilities and turn on iCloud and tick keyValueStorage (not core data). Done.

Note: To take it even a step further you could get the KeychainWrapper helper from JRendel on gitHub. Than instead of using NSUserDefaults to store the encoded gameData you use keychain, its dead simple to use.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...