NSUserDefaults not simply saving high score - swift

Its supposed to be that when my player reaches a new high score, it'll show it on the GameOver scene, but in this case, its not doing that. I have all of my NSUserDefaults in a separate .swift file.
Here is my code:
import Foundation
class GameState {
var score: Int
var highScore: Int
var stars: Int
class var sharedInstance :GameState {
struct Singleton {
static let instance = GameState()
}
return Singleton.instance
}
init() {
// Init
score = 0
highScore = 0
stars = 0
// Load game state
let defaults = NSUserDefaults.standardUserDefaults()
highScore = defaults.integerForKey("highScore")
stars = defaults.integerForKey("stars")
}
func saveState() {
// Update highScore if the current score is greater
highScore = max(score, highScore)
// Store in user defaults
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setInteger(highScore, forKey: "highScoreLabel")
defaults.setInteger(stars, forKey: "stars")
NSUserDefaults.standardUserDefaults().synchronize()
}
}
In my GameOver scene, it I have all the text labels set up like so:
lblHighScore.text = String(format: "High Score: %d", GameState.sharedInstance.highScore)
And in my GameScene, I have it so that it should synchronize when the game ends:
func endGame() {
gameOver = true
GameState.sharedInstance.saveState() //right here
print("saved score")
let reveal = SKTransition.fadeWithDuration(0.5)
let gameScene = GameOver(size: self.scene!.size)
view!.presentScene(gameScene, transition: reveal)
print("new scene")
}
Is something missing? Will post more code if necessary.

There is an issue in your coding you may have overlooked:
You are saving as "highScoreLabel"
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setInteger(highScore, forKey: "highScoreLabel")
defaults.setInteger(stars, forKey: "stars")
defaults.synchronize()
But trying to read as "highScore"
highScore = defaults.integerForKey("highScore")
You should use the same key when reading from and writing to NSUserDefaults
For Future Reference...
Personally I set static let variables when dealing with keys. So you never come to this hiccup in the future.
struct Defaults {
static let HighScore = "highScore"
static let Stars = "stars"
}
and then use
highScore = defaults.integerForKey(Defaults.HighScore)
The issue was that none of the variables were being changed, code replaced with:
class GameState {
var highScore: Int
var stars: Int
class var sharedInstance :GameState {
struct Singleton {
static let instance = GameState()
}
return Singleton.instance
}
init() {
// Load game state
highScore = NSUserDefaults.standardUserDefaults().integerForKey(Defaults.HighScore) ?? 0
stars = NSUserDefaults.standardUserDefaults().integerForKey(Defaults.Stars) ?? 0
}
func saveState(score: Int) {
// Update highScore if the current score is greater
highScore = max(score, highScore)
// Store in user defaults
NSUserDefaults.standardUserDefaults().setInteger(highScore, forKey: Defaults.HighScore)
NSUserDefaults.standardUserDefaults().setInteger(stars, forKey: Defaults.Stars)
NSUserDefaults.standardUserDefaults().synchronize()
}
}

Related

Storing Collective Level Score Values For An Overall "Bank" Value in SpriteKit

I have a game that has 9 different levels which have their own highscore values for coins collected and special coins collected. I want to make a "Bank" that can store those values and add them up to be able to 'purchase' the unlock for some levels. I'm not sure if I implemented my scoring system in a way that won't allow this or if I'm missing something simple. Any insight is greatly appreciated!
import Foundation
struct ScoreManager {
static func getCurrentScore(for levelKey: String) -> [String:Int] {
if let existingData = UserDefaults.standard.dictionary(forKey: levelKey) as? [String:Int] {
return existingData
} else {
return [GameConstants.StringConstants.scoreScoreKey:0, GameConstants.StringConstants.scoreStarsKey:0, GameConstants.StringConstants.scoreCoinsKey:0]
}
}
static func updateScore(for levelKey: String, and score: [String:Int]) {
UserDefaults.standard.set(score, forKey: levelKey)
UserDefaults.standard.synchronize()
}
static func compare(scores: [[String:Int]], in levelKey: String) {
var newHighscore = false
let currentScore = getCurrentScore(for: levelKey)
var maxScore = currentScore[GameConstants.StringConstants.scoreScoreKey]!
var maxStars = currentScore[GameConstants.StringConstants.scoreStarsKey]!
var maxCoins = currentScore[GameConstants.StringConstants.scoreCoinsKey]!
for score in scores {
if score[GameConstants.StringConstants.scoreScoreKey]! > maxScore {
maxScore = score[GameConstants.StringConstants.scoreScoreKey]!
newHighscore = true
}
if score[GameConstants.StringConstants.scoreStarsKey]! > maxStars {
maxStars = score[GameConstants.StringConstants.scoreStarsKey]!
newHighscore = true
}
if score[GameConstants.StringConstants.scoreCoinsKey]! > maxCoins {
maxCoins = score[GameConstants.StringConstants.scoreCoinsKey]!
newHighscore = true
}
}
if newHighscore {
let newScore = [GameConstants.StringConstants.scoreScoreKey: maxScore, GameConstants.StringConstants.scoreStarsKey: maxStars, GameConstants.StringConstants.scoreCoinsKey: maxCoins]
updateScore(for: levelKey, and: newScore)
}
}
And this is called in the GameScene after you finish the level..
func finishGame() {
gameState = .finished
var stars = 0
let percentage = CGFloat(coins)/100.0
if percentage >= 0.8 {
stars = 3
} else if percentage >= 0.4 {
stars = 2
} else if coins >= 1 {
stars = 1
}
let scores = [
GameConstants.StringConstants.scoreScoreKey: coins,
GameConstants.StringConstants.scoreStarsKey: stars,
GameConstants.StringConstants.scoreCoinsKey: superCoins
]
ScoreManager.compare(scores: [scores], in: levelKey)
createAndShowPopup(type: 1, title: GameConstants.StringConstants.completedKey)
if level < 9 {
let nextLevelKey = "Level_\(world)-\(level+1)_Unlocked"
UserDefaults.standard.set(true, forKey: nextLevelKey)
UserDefaults.standard.synchronize()
}
}
Ignore the stars, I used these to show basically how well you did on the level. I'll gladly provide more code snippets if needed too. Thank you all again!

Using UserDefaults to save a high score in swift

I"m trying to get a high score to save each time the player exits the app, but I'm struggling to get it to work and I'm confused as to where I'm making a mistake.
var gameSettings = Settings.sharedInstance
let HIGHSCORE = "HIGHSCORE"
if gameSettings.highScore < score{
gameSettings.highScore = score
UserDefaults.standard.set(gameSettings.highScore, forKey: HIGHSCORE)
}
if gameSettings.highScore >= score{
gameSettings.score = score
}
let gameOverHighScore = SKLabelNode()
gameOverHighScore.name = "gameOverHighScore"
gameOverHighScore.fontName = "Helvetica Neue UltraLight"
if let HIGHSCORE = UserDefaults.standard.value(forKey: HIGHSCORE) {
gameOverHighScore.text = "HIGHSCORE : \(HIGHSCORE)M"
}
gameOverHighScore.fontColor = SKColor.white
gameOverHighScore.position = CGPoint(x: 0, y: -175)
gameOverHighScore.fontSize = 70
gameOverHighScore.zPosition = 52
addChild(gameOverHighScore)
In if let HIGHSCORE = UserDefaults.standard.value(forKey: HIGHSCORE) {
gameOverHighScore.text = "HIGHSCORE : \(HIGHSCORE)M"
}
you are try find value in UserDefaults not by key HIGHSCORE, but by new value HIGHSCORE of type ANY. If you want to fix it, you can do like this:
if let HIGHSCORE = UserDefaults.standard.value(forKey: self. self.HIGHSCORE) as! Int {
gameOverHighScore.text = "HIGHSCORE : \(HIGHSCORE)M" }
or you can unwrap value in new value with another name. And don't forget to cast value to appropriate type or use
UserDefaults.standard.integer(forKey: self.HIGHSCORE)
Here is a simple example on how to do it:
let firebaseId = user.uid // Variable we want to save
UserDefaults.standard.set(firebase, forKey: "firebase") // Saving the variable with key "firebase"
And now to retrieve:
if let firebaseId = UserDefaults.standard.string(forKey: "firebase") {
print("Firebase ID: \(firebase)")
}
On your code, you are retrieving to HIGHSCORE, and you should be retrieving to something like gameSettings.highScore (Based on what I can infer from your code)

Spritekit game freezes for a second the first time it retrieves a high score

edit: I've found that my game freezes while it retrieves the high score for the first time AND it freezes everytime a new high score is archived. Is there a less intensive way of doing this that won't freeze my game? Thanks.
I've been looking around for reasons why a game might freeze during a first collision. The game does not freeze when the ball collides with objects that do not cause the player to lose, but rather the first time that the player loses. All I found online was that playing an audio file without properly preloading it could be the cause. In order to verify my music isn't the issue, I removed my background music from the game entirely and the problem persists. Below is the code that is called when the game freezes. Again, the freezing only occurs the first time this code is called. I'd appreciate any sort of help on this. Thanks.
The gameOver function:
func gameOver() {
state = .FSGameStateEnded
myScore.highScore = score
if retrievedHighScore.highScore < myScore.highScore
{
SaveHighScore().ArchiveHighScore(highScore: myScore)
}
retrievedHighScore = SaveHighScore().RetrieveHighScore() as! HighScore
label_highScore.text = "High Score: \(retrievedHighScore.highScore)"
label_finalScore.text = "Your Score: \(score)"}
}
Here is my code for retrieving and saving high scores:
class HighScore: NSObject {
var highScore: Int = 0
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeInteger(highScore, forKey: "highScore")
}
init(coder aDecoder: NSCoder!) {
highScore = aDecoder.decodeIntegerForKey("highScore")
}
override init() {
}
}
class SaveHighScore:NSObject {
var documentDirectories:NSArray = []
var documentDirectory:String = ""
var path:String = ""
func ArchiveHighScore(#highScore: HighScore) {
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as! String
path = documentDirectory.stringByAppendingPathComponent("highScore.archive")
if NSKeyedArchiver.archiveRootObject(highScore, toFile: path) {
println("Success writing to file!\(highScore.highScore)")
} else {
println("Unable to write to file!")
}
}
func RetrieveHighScore() -> NSObject {
var dataToRetrieve = HighScore()
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as! String
path = documentDirectory.stringByAppendingPathComponent("highScore.archive")
if let dataToRetrieve2 = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? HighScore {
dataToRetrieve = dataToRetrieve2
}
return(dataToRetrieve)
}
}

NSUserDefaults for high score is not working on iOS 8 Simulator?

I've tried to synchronize the saved data but my game still does not store the highest score. I can't find whats wrong with the replacing of the highScore with score if it is higher. Sorry I'm a beginner who just started learning iOS programming.
init(size: CGSize, score: NSInteger) {
super.init(size: size)
self.backgroundColor = SKColor.whiteColor()
//To save highest score
var highscore = 0
let userDefaults = NSUserDefaults.standardUserDefaults()
if (score > highscore){
NSUserDefaults.standardUserDefaults().setObject(score, forKey: "highscore")
NSUserDefaults.standardUserDefaults().synchronize()
}
var savedScore: Int = NSUserDefaults.standardUserDefaults().objectForKey("highscore") as! Int
//To get the saved score
var savedScore: Int = NSUserDefaults.standardUserDefaults().objectForKey("highscore") as! Int
Let's step through your code.
First, you overwrite whatever the high score was with 0:
//To save highest score
let highscore = 0
let userDefaults = NSUserDefaults.standardUserDefaults()
NSUserDefaults.standardUserDefaults().setObject(highscore, forKey: "highscore")
NSUserDefaults.standardUserDefaults().synchronize()
Then, you're checking if "highscore" is in the defaults:
if let highscore: AnyObject = userDefaults.valueForKey("highscore") {
A few notes:
This will always be true because you just set it
You should be using objectForKey(_:), not valueForKey(_:)
But really you should be using integerForKey(_:) not objectForKey(_:), since highscore is always an Int.
You can delete : AnyObject because objectForKey(_:) returns AnyObject?
Then, we go into the code:
NSUserDefaults.standardUserDefaults().setObject(score, forKey: "highscore")
NSUserDefaults.standardUserDefaults().synchronize()
You are now overwriting the 0 score you just added earlier with whatever score is.
Finally, your else block, which will never get called, sets highscore, even though you just did that before the if condition.
}
else {
NSUserDefaults.standardUserDefaults().setObject(highscore, forKey: "highscore")
NSUserDefaults.standardUserDefaults().synchronize()
}
It's not clear from your code what you want to do, but you did say this in your question:
I can't find whats wrong with the replacing of the highScore with score if it is higher.
Well, for one thing, you're never checking which one is higher.
I think you might be trying to do this:
let defaults = NSUserDefaults.standardUserDefaults()
let highscore = defaults.integerForKey("highscore")
if score > highscore {
defaults.setInteger(score, forKey: "highscore")
defaults.synchronize()
}

Saving highscores with NSUserDefaults

I'm trying to save the highscore of my game. I'm trying to do this via NSUserDefaults. This is the code I'm using:
//To save highest score
var highestScore:Int = 20
NSUserDefaults.standardUserDefaults().setObject(highestScore, forKey:"HighestScore")
NSUserDefaults.standardUserDefaults().synchronize()
//To get the saved score
var savedScore: Int = NSUserDefaults.standardUserDefaults().objectForKey("HighestScore") as Int
println(savedScore)
But I get an error with NSUserDefaults saying "Expected declaration" and I can't figure out how to properly implement this.
Or should I be using NSArchiver for this? And if that is the case how could I implement this?
Use NSCoding. Create a Swift file "HighScore"
import Foundation
class HighScore: NSObject {
var highScore: Int = 0
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeInteger(highScore, forKey: "highScore")
}
init(coder aDecoder: NSCoder!) {
highScore = aDecoder.decodeIntegerForKey("highScore")
}
override init() {
}
}
class SaveHighScore:NSObject {
var documentDirectories:NSArray = []
var documentDirectory:String = ""
var path:String = ""
func ArchiveHighScore(#highScore: HighScore) {
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("highScore.archive")
if NSKeyedArchiver.archiveRootObject(highScore, toFile: path) {
println("Success writing to file!")
} else {
println("Unable to write to file!")
}
}
func RetrieveHighScore() -> NSObject {
var dataToRetrieve = HighScore()
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("highScore.archive")
if let dataToRetrieve2 = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? HighScore {
dataToRetrieve = dataToRetrieve2
}
return(dataToRetrieve)
}
}
Then for your ViewController:
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
var Score = HighScore()
override func viewDidLoad() {
super.viewDidLoad()
Score.highScore = 100
SaveHighScore().ArchiveHighScore(highScore: Score)
var retrievedHighScore = SaveHighScore().RetrieveHighScore() as HighScore
println(retrievedHighScore.highScore)
}
}