NSUserDefaults Saving Score - ios

I am looking for some advice when it comes to saving a score in my app - I currently have a coin count working in which through my HUD class when I collide through Coins the value increases accordingly. My next step is trying to initiate a high-score that initiates, after some research I think the topic I am looking into is based around NSUserDefaults..
The current code :
import SpriteKit
class HUD: SKNode {
//An SKLabelNode to print the coin score:
let coinCountText = SKLabelNode(text: "000000")
func createHUDNodes(){
// Configure the coin text label:
coinCountText.fontName = "STHupo-Heavy-Italic"
let coinTextPosition = CGPoint(x: -cameraOrigin.x + 770, y: coinPosition.y)
coinCountText.position = coinTextPosition
coinCountText.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.left
coinCountText.verticalAlignmentMode = SKLabelVerticalAlignmentMode.center
// Add the text label and coin icon to the HUD:
self.addChild(coinCountText)
}
func setCoinCountDisplay(newCoinCount:Int) {
let formatter = NumberFormatter()
let number = NSNumber(value: newCoinCount)
formatter.minimumIntegerDigits = 6
if let coinStr =
formatter.string(from: number) {
// Update the label node with the new count:
coinCountText.text = coinStr
}
}
Working in the HUD class I think naturally I would need to create another 'SKlabelNode'for the highscore node which I can duplicate from the code that works above.. my problem is how can I implement a highscore feature in the app, as when closed and re-opening the application it will remember the highscore..
I assume the following code would need to be added but I do not understand how it ties together in the class.
var currentHighScore =
NSUserDefaults.standardUserDefaults().integerForKey("coin_highscore")
var highScoreLabel = SKLabelNode(fontNamed:"STHupo-Heavy-Italic")
highScoreLabel.text = ""
highScoreLabel.fontSize = 45
highScoreLabel.position = CGPoint(x: viewSize.width * 0.5, y: viewSize.height * 0.30)
self.addChild(highScoreLabel)
if (score > currentHighScore) {
NSUserDefaults.standardUserDefaults().setInteger(score, forKey: "player_highscore")
NSUserDefaults.standardUserDefaults().synchronize()
highScoreLabel.text = "New High Score: \(score) !"
} else {
highScoreLabel.text = "Better Luck Next Time: \(score)"
}
}
Any advice on how I could integrate this code would be much appreciated.
, AJ
Revised :
else if nodeTouched.name == "returnToMenu"{
// Transition to the main menu scene
self.view?.presentScene(MenuScene(size: self.size),transition.crossFade(withDuration: 0.6))
let currentHighScore = UserDefaults.standard.integer(forKey: "Player_highscore")
let highScoreLabel = SKLabelNode(fontNamed:"STHupo-Heavy-Italic")
highScoreLabel.text = "Value: \ (String(describing: coin.value))"
highScoreLabel.text = ""
highScoreLabel.fontSize = 45
highScoreLabel.zPosition = 100
highScoreLabel.position = CGPoint(x: 50, y: 50)
self.addChild(highScoreLabel)
if (coin.value > currentHighScore) {
UserDefaults.standard.set(coin.value, forKey: "Player_highscore")
UserDefaults.standard.synchronize()
highScoreLabel.text = "New High Value: \(String(describing: coin.value)) !"
} else {
highScoreLabel.text = "Better Luck Next Time: \(String(describing: coin.value))"
}
}
}
}
I am assuming it's the referencing to my original HUD that is causing the problem? In my coin class I declared the coin.value = 5, each time the coin is collected by coinCountDisplay increases accordingly. However still does not show - :/

Add a property to your view controller with both get and set and use it in all the places. The get method should get the value from UserDefaults and the set should update the value in UserDefaults.
var coinHighscore: Int {
get {
UserDefaults.standard.integer(forKey: "coin_highscore")
}
set {
UserDefaults.standard.set(newValue, forKey: "coin_highscore")
}
}

Extending on Frankenstein's answer, IF you want to use Swift 5 you can use Property Wrappers to avoid writing get and set every time:
#propertyWrapper struct UserDefaultsBacked<Value> {
let key: String
let defaultValue: Value
var storage: UserDefaults = .standard
var wrappedValue: Value {
get {
let value = storage.value(forKey: key) as? Value
return value ?? defaultValue
}
set {
storage.setValue(newValue, forKey: key)
}
}
}
And use it like this:
#UserDefaultsBacked(key: "coin_highscore", defaultValue: 0)
var coinHighscore: Int

Related

Developing an ARKit app that leaves text for others to view

I am creating an iOS AR app that sets text in a specific location and leaves it there for others to view. Is there a better way to implement it than what I am doing?
Currently, I have it set so that the text is saved to Firebase and loads it by setting the nodes relative to the camera’s position. I’m wondering if there is a way to save ARAnchors in a fashion similar to what I am doing but is that possible?
My current function for saving the text to the location via a user tapping the screen:
/*
* Variables for saving the user touch
*/
var touchX : Float = 0.0
var touchY : Float = 0.0
var touchZ : Float = 0.0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// will be used for getting the text
let textNode = SCNNode()
var writing = SCNText()
// gets the user’s touch upon tapping the screen
guard let touch = touches.first else {return}
let result = sceneView.hitTest(touch.location(in: sceneView), types: [ARHitTestResult.ResultType.featurePoint])
guard let hitResult = result.last else {return}
let hitTransform = SCNMatrix4.init(hitResult.worldTransform)
let hitVector = SCNVector3Make(hitTransform.m41, hitTransform.m42, hitTransform.m43)
// saves X, Y, and Z coordinates of touch relative to the camera
touchX = hitTransform.m41
touchY = hitTransform.m42
touchZ = hitTransform.m43
// Was thinking of adding the ability to change colors. Probably can skip next seven lines
var colorArray = [UIColor]()
colorArray.append(UIColor.red)
writing = SCNText(string: input.text, extrusionDepth: 1)
material.diffuse.contents = colorArray[0]
writing.materials = [material]
// modifies the node’s position and size
textNode.scale = SCNVector3(0.01, 0.01, 0.01)
textNode.geometry = writing
textNode.position = hitVector
sceneView.scene.rootNode.addChildNode(textNode)
// last few lines save the info to Firebase
let values = ["X" : touchX, "Y" : touchY, "Z" : touchZ, "Text" : input.text!] as [String : Any]
let childKey = reference.child("Test").childByAutoId().key
if input.text != nil && input.text != "" {
let child = reference.child("Test").child(childKey!)
child.updateChildValues(values)
} else {
let child = reference.child("Test").child(childKey!)
child.updateChildValues(values)
} // if
} // override func
/*
* Similar to the previous function but used in next function
*/
func placeNode(x: Float, y: Float, z: Float, text: String) -> Void {
let textNode = SCNNode()
var writing = SCNText()
let hitVector = SCNVector3Make(x, y, z)
touchX = x
touchY = y
touchZ = z
var colorArray = [UIColor]()
colorArray.append(UIColor.red)
writing = SCNText(string: text, extrusionDepth: 1)
material.diffuse.contents = colorArray[0]
writing.materials = [material]
textNode.scale = SCNVector3(0.01, 0.01, 0.01)
textNode.geometry = writing
textNode.position = hitVector
sceneView.scene.rootNode.addChildNode(textNode)
} // func
/*
* This next function is used in my viewDidLoad to load the data
*/
func handleData() {
reference.child("Test").observeSingleEvent(of: .value, with: { (snapshot) in
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let xCoord = Float(truncating: child.childSnapshot(forPath: "X").value as! NSNumber)
let yCoord = Float(truncating: child.childSnapshot(forPath: "Y").value as! NSNumber)
let zCoord = Float(truncating: child.childSnapshot(forPath: "Z").value as! NSNumber)
let inscription = child.childSnapshot(forPath: "Text").value
self.placeNode(x: xCoord , y: yCoord , z: zCoord , text: inscription as! String)
} // for
} // if
}) // reference
} // func
I have looked into a few things such as ARCore but that looks like it uses Objective-C. I’ve made this app in Swift and I am not sure if I can incorporate ARCore with how I have implemented my current application.
Do I just need to get over it and learn Objective-C? Can I still work with what I have?
I think that ARCore anchors are only available for 24 hours, so that could be a problem.
You probably need to use ARKit2.0's ARWorldMap and save it as data on firebase for others to see the text in the same place, otherwise you are assuming in your code that future users will start their AR session in the exact same position and direction as the person who left the text. You probably need to use core location first to see where in the world the user is.

An array of a value not being properly displayed in the label

I am using xcode 8 and swift 3. I have made a mini game where you have to answer questions as fast as you can. You are timed by a stopwatch. I have the code to actually store the times(eg 23 seconds) but instead of storing the time, it replaces it with "Label". My stopwatch label is called down and the label that displays the values is called resultLabel I have been told the problem is when i set the text of the label. Any ideas on where i need to fix it. An example is ["Label", "Label", "Label"]
Stopwatch code:
if timerSecond != nil {
timerSecond?.invalidate()
timerSecond = nil
} else {
timerSecond = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
self.currentTime += 1
let minutePortion = String(format: "%02d", self.currentTime / 60 )
let secondsPortion = String(format: "%02d", self.currentTime % 60 )
self.down.text = "\(minutePortion):\(secondsPortion)"
}
}
extension for User Defaults:
extension UserDefaults {
var timesSecond: [String] {
get {
if let times = UserDefaults.standard.object(forKey: "times") as? [String] {
return times
} else {
return []
}
}
set {
UserDefaults.standard.set(newValue, forKey: "times")
}
}
}
Button code to display the array of values:
let arrayOfTimes = UserDefaults.standard.timesSecond
resultLabel.text = "\(arrayOfTimes)"
code for storing the data:
UserDefaults.standard.timesSecond.append(resultLabel.text!)

detecting if a spritenode exists in swift

I am trying to detect if one of the nodes that I have made through a subclass of SKNode (called Achievements) exists and if it doesn't exist then i'm trying to turn off a boolean variable.
What I use to locate the SKShapeNode (called "Indicator")
func checkIndicatorStatus() {
moveableArea.enumerateChildNodes(withName: "Achievement") {
(node, stop) in
let Indicate = node
Indicate.enumerateChildNodes(withName: "Indicator") {
node, stop in
if let Achievement = node as? Achievements {
menuAchieveNotificationOn = false
}
}
}
}
I have enumerated through the nodes specifically and tried searching for it but it doesn't seem to do anything. what am I doing wrong?
Here is my subclass. I have many of them named achievement displayed in my scene.
class Achievements: SKNode {
//Nodes used throughout the SKNode class
var achievementLabel = SKLabelNode()
var achievementTitleLabel = SKLabelNode()
var achievementNode = SKSpriteNode()
var notifyCircle = SKShapeNode()
//Amount Variables used as Achievement Properties
var image: String = ""
var information: String = ""
var title: String = ""
var amount = 0
var neededAmount = 0
var notification:Bool = false
var stage = 0
func getachievementData(AchName: String) {
let getDataRequest:NSFetchRequest<Achievement> = Achievement.fetchRequest()
getDataRequest.predicate = NSPredicate(format: "theSearchName == %#" , AchName)
do {
let searchResults = try CoreDatabaseContoller.getContext().fetch(getDataRequest)
//print("number of results: \(searchResults.count)")
for result in searchResults as [Achievement] {
title = result.theName!
information = result.theDescription!
image = result.theImage!
amount = Int(result.aAmount!)
neededAmount = Int(result.aNeededAmount!)
stage = Int(result.aStage!)
if result.aHasBeenAchieved!.intValue == 1 {
notification = true
}
}
}
catch {
print("ERROR: \(error)")
}
createAchievement()
}
func createAchievement() {
let tex:SKTexture = SKTexture(imageNamed: image)
achievementNode = SKSpriteNode(texture: tex, color: SKColor.black, size: CGSize(width: 85, height: 85)) //frame.maxX / 20, height: frame.maxY / 20))
achievementNode.zPosition = -10
achievementSprites.append(achievementNode)
self.name = "Achievement"
self.addChild(achievementNode)
self.zPosition = -11
createAchievementLabels()
}
func createAchievementLabels() {
achievementTitleLabel = SKLabelNode(fontNamed: "Avenir-Black")
achievementTitleLabel.fontColor = UIColor.black;
achievementTitleLabel.fontSize = 15 //self.frame.maxY/30
achievementTitleLabel.position = CGPoint (x: 0, y: 50)
achievementTitleLabel.text = "\(title)"
achievementTitleLabel.zPosition = -9
addChild(achievementTitleLabel)
achievementLabel = SKLabelNode(fontNamed: "Avenir-Black")
achievementLabel.fontColor = UIColor.black;
achievementLabel.fontSize = 13 //self.frame.maxY/30
achievementLabel.position = CGPoint (x: 0, y: -55)
achievementLabel.text = ("\(amount) / \(neededAmount)")
achievementLabel.zPosition = -9
addChild(achievementLabel)
if notification == true {
notifyCircle = SKShapeNode(circleOfRadius: 10)
notifyCircle.fillColor = .red
notifyCircle.position = CGPoint(x: 30 , y: 35)
notifyCircle.zPosition = 1000
notifyCircle.name = "Indicator"
addChild(notifyCircle)
}
}
}
EDIT 1
As you can see from the image below, there are a number of different achievement nodes each with their individual names and unlock criteria, when an achievement becomes unlocked it makes an indicator and changes the graphic, which is seen for the two X nodes here with the coloured red circle in the top right corners of each of them which is the "indicator" (if you look at the subclass at the bottom of it the creation of the indicator is there)
Now as you can seen the big red button in the bottom right hand corner of the picture is the achievement menu button which also has an indicator which is controlled by the bool variable (menuAchieveNotificationOn) what i'm trying to achieve is once the achievements with the indicators have each been pressed they are removed from the node.
what i'm trying to do is search each of the nodes to see if the indicator still exists if not I want to turn the variable (menuAchieveNotificationOn) to false.
You should be able to use this:
if let indicatorNode = childNode(withName: "//Indicator") as! SKShapeNode? {
menuAchieveNotificationOn = false
} else {
menuAchieveNotificationOn = true
}
EDIT: to run some code for EVERY "indicator" node in the scene. If any are found, achievementsFound is set to true:
achievementsFound = false
enumerateChildNodes(withName: "//Indicator") { indicatorNode, _ in
// Do something with indicatorNode
achievementsFound = true
}
Although this seems too simple, so I might have misunderstood your aim.

Increment score in Sprite Kit to show in HUD

I know I'm close in getting the getting the score to increment in my game.
When I explicitly change the code to add a integer (5 for example) instead of "%d", the score shows in the HUD when a coin is touched :
func didBeginContact(contact: SKPhysicsContact) {
lblScore.text = String(format: "%d", GameState.sharedInstance.score)
}
to:
func didBeginContact(contact: SKPhysicsContact) {
lblScore.text = String(format: "5", GameState.sharedInstance.score)
}
However if I leave the "%d", then nothing happens. I'm not sure how to increment the score in the HUD or where to make changes.
Here's the rest of the code.
GameScene.swift:
struct PhysicsCategory {
static let None: UInt32 = 0
static let Player: UInt32 = 0b1
static let CoinNormal: UInt32 = 0b1000
static let CoinSpecial: UInt32 = 0b10000
}
class GameScene: SKScene, SKPhysicsContactDelegate {
// HUD
var hudNode: SKNode!
var lblScore: SKLabelNode!
var lblCoins: SKLabelNode!
override func didMoveToView(view: SKView) {
// HUD
hudNode = SKNode()
hudNode.zPosition = 1000
cameraNode.addChild(hudNode)
// Coins
// 1
let coin = SKSpriteNode(imageNamed: "powerup05_1")
coin.position = convertPoint(CGPoint(x: 300, y: self.size.height-100), toNode: cameraNode)
coin.zPosition = 1000
hudNode.addChild(coin)
// 2
lblCoins = SKLabelNode(fontNamed: "ChalkboardSE-Bold")
lblCoins.fontSize = 70
lblCoins.fontColor = SKColor.whiteColor()
lblCoins.position = convertPoint(CGPoint(x: 375, y: self.size.height-100), toNode: cameraNode)
lblCoins.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.Left
lblCoins.zPosition = 1000
// 3
lblCoins.text = String(format: "X %d", GameState.sharedInstance.coins)
hudNode.addChild(lblCoins)
// Score
// 4
lblScore = SKLabelNode(fontNamed: "ChalkboardSE-Bold")
lblScore.fontSize = 70
lblScore.fontColor = SKColor.whiteColor()
lblScore.position = convertPoint(CGPoint(x: self.size.width-325, y: self.size.height-100), toNode: cameraNode)
lblScore.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.Right
lblScore.zPosition = 1000
// 5
lblScore.text = "0"
hudNode.addChild(lblScore)
}
}
func didBeginContact(contact: SKPhysicsContact) {
lblScore.text = String(format: "%d", GameState.sharedInstance.score)
}
GameState.swift:
class GameState {
var score: Int
var highScore: Int
var coins: Int
init() {
// Init
score = 0
highScore = 0
coins = 0
// Load game state
let defaults = NSUserDefaults.standardUserDefaults()
highScore = defaults.integerForKey("highScore")
coins = defaults.integerForKey("coins")
}
func saveState() {
// Update highScore if the current score is greater
highScore = max(score, highScore)
score = max(score, highScore)
// Store in user defaults
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setInteger(highScore, forKey: "highScore")
defaults.setInteger(coins, forKey: "coins")
NSUserDefaults.standardUserDefaults().synchronize()
}
class var sharedInstance: GameState {
struct Singleton {
static let instance = GameState()
}
return Singleton.instance
}
}
As I can see you are using GameState.sharedInstance.score to store a score. But you never update it. You have to increment the score each time when player scores. This part:
lblScore.text = String(format: "%d", GameState.sharedInstance.score)
only reads from a score variable.
Also this part doesn't make sense:
highScore = max(score, highScore)
score = max(score, highScore) //remove this
score variable is not the same as highscore variable.
I was able to increment the score by this line:
GameState.sharedInstance.score += 1
Or replace 1 with another number by how many points you want to increment.

Saving And Loading Up A Highscore In Swift Using NSUserDefaults

I am having some problems while attempting to compare my score and highscore to see which one is bigger and then save it. I think the problem lies in the comparison and saving of the highscore and score. Help would be greatly appreciated!
//Comparison between score and highscore
if score > highscore {
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
highscore = defaults.valueForKey("highscore")?.integerValue ?? 0
defaults.setInteger(score, forKey: "highscore")
defaults.synchronize()
highscoreString = String(highscore)
highscoreLabel.text = highscoreString }
Full Page Code - https://github.com/SRL311/Highscore/blob/master/Highscore
(Sorry about the confusing capitals and lowercases there)
You can also create a new NSUserDefault property with a getter and a setter to automatically check if the score you are trying to save it is higher than the actual high score:
update: Xcode 8.2.1 • Swift 3.0.2
extension UserDefaults {
static let highScoreIntegerKey = "highScoreInteger"
static let highScoreDoubleKey = "highScoreDouble"
var highScore: Int {
get {
print("High Score:", integer(forKey: UserDefaults.highScoreIntegerKey))
return integer(forKey: UserDefaults.highScoreIntegerKey)
}
set {
guard newValue > highScore
else {
print("\(newValue) ≤ \(highScore) Try again")
return
}
set(newValue, forKey: UserDefaults.highScoreIntegerKey)
print("New High Score:", highScore)
}
}
func resetHighScore() {
removeObject(forKey: UserDefaults.highScoreIntegerKey)
print("removed object for highScoreIntegerKey")
}
var highScoreDouble: Double {
get {
return double(forKey: UserDefaults.highScoreDoubleKey)
}
set {
guard newValue > highScoreDouble
else {
print("\(newValue) ≤ \(highScoreDouble) Try again")
print("Try again")
return
}
set(newValue, forKey: UserDefaults.highScoreDoubleKey)
print("New High Score:", highScoreDouble)
}
}
func resetHighScoreDouble() {
removeObject(forKey: UserDefaults.highScoreDoubleKey)
print("removed object for highScoreDoubleKey")
}
}
testing in a Project (doesn't work in playground since Swift 2):
UserDefaults().resetHighScore()
UserDefaults().highScore // High Score = 0
UserDefaults().highScore = 100 // New High Score = 100
UserDefaults().highScore = 50 // 50 < 100 Try again
UserDefaults().highScore // 100
UserDefaults().highScore = 150 // New High Score = 150
UserDefaults().highScore // 150
Xcode 7.2 • Swift 2.1.1
extension NSUserDefaults {
var highScore: Int {
get {
print("High Score = " + integerForKey("highScore").description)
return integerForKey("highScore")
}
set {
guard newValue > highScore else { print("\(newValue) ≤ \(highScore) Try again")
return
}
setInteger(newValue, forKey: "highScore")
print("New High Score = \(highScore)")
}
}
func resetHighScore() {
removeObjectForKey("highScore")
print("removed object for key highScore")
}
var highScoreDouble: Double {
get {
return doubleForKey("highScoreDouble")
}
set {
guard newValue > highScoreDouble else { print("Try again")
return
}
setDouble(newValue, forKey: "highScoreDouble")
print("New High Score = \(highScoreDouble)")
}
}
func resetHighScoreDouble() {
removeObjectForKey("highScoreDouble")
print("removed object for key highScoreDouble")
}
}
Read your code carefully and you should be able to find the errors.
You are not assigning score to highscore.
There is no reason to get the old highscore from NSUserDefaults.
There is no need to synchronize.
Swift 2: You have set an integer value in userDefaults so, you may try retrieve it using, defaults.integerForKey(score, forKey: "highscore")

Resources