Swift, SpriteKit, pass variable to other scene - swift

I created a score variable in my GameScene. Now I want to see the result in another scene, for example GameOverScene.
How I can do it ?

You need always try to post some code on stack overflow.
There is loads of way to do what you want.
1) You could use NSUserDefaults, to save the score and access the saved property in another scene and than assigns it to a new score variable.
2) You could make the score property static, so in gameScene you would say
static var score = 0
and than anywhere in your project you can say
GameScene.score = 5
Remember to reset your score to 0 after every game because static properties are having 1 instance only, i.e. they exist for the lifetime of the app.
3) Yet another way is to do a singleton class
class GameData {
static let shared = GameData()
var score = 0
private init() { }
}
Than in your SKscenes you either say
let gameData = GameData.shared
gameData.score = 5
or
GameData.shared.score = 5

Related

Accessing A Variable In UserDefaults Swift 3

This may seem like a basic question but I don't know the answer. In my app, you earn coins after every play of the game. Here is my code for earning coins and saving them
let defaultCoins = UserDefaults()
var coinNumber = defaultCoins.integer(forKey: "coinSaved")
coinNumber = coinNumber + score
defaultCoins.set(coinNumber, forKey: "coinSaved")
I would like to be able to access coinNumber from a different view controller and be able to use it. So maybe make a variable = coinNumber that I can access anywhere. I have a shop and would like to be able to purchase a new character when the user has 1000 coins but currently cant do that as I don't know how to access coinNumber from my shopViewController
Since you're saving the data to UserDefaults it doesn't seem like you should need to propagate it between view controllers - just use the defaults as your 'source of truth' for the correct value. Instead of saving it to a custom UserDefaults object, however, just save it to the standard one that is created for all iOS/OS X apps: UserDefaults.standard
Just read it from it whenever you need it the way you're already doing above. A couple things to note about this approach:
You can use Key/Value observing to detect changes to the value when other controllers modify it, but you'll need to be careful as KVO can be easy to crash if you forget to remove observers properly.
You should probably call synchronize on it which forces the changes you've made to disk. This will make sure that if your app is closed suddenly that you've secured the data.
UserDefaults is great for when you have less than 10 pieces of data you're wanting to save, like a highest score or a set of preferences (that's what it's made to do), but it's not great for large data structures. You should look into CoreData or KeyedArchiver for bigger stuff.
Pro-tip: Rather than using the loose string "coinSaved" over and over again, define a static constant somewhere, or use an enumeration of a string type to pre-define all your values once. Then you get code completion and avoid errors.
So, your code above would look more like:
In one view controller:
let defaultCoins = UserDefaults.standard
var coinNumber = defaultCoins.integer(forKey: "coinSaved")
coinNumber = coinNumber + score
defaultCoins.set(coinNumber, forKey: "coinSaved")
defaultCoins.synchronize()
Then, in another controller where you need to read it:
let defaultCoins = UserDefaults.standard
let coinNumber = defaultCoins.integer(forKey: "coinSaved")
Your main problem is that you are creating a new UserDefaults object whenever you try to access it, you should either use the UserDefaults.standard instance or create one with a specific identifier.
I would also suggest creating a singleton to manage the coins if you are going to use them in several view controllers, this is to save your time writing the same safety checks over and over (like making sure you can't spend more coins than you have)
It would look something like this:
class CoinManager {
static let shared = CoinManager()
private var _coins: UInt {
didSet {
UserDefaults.standard.set(_coins, forKey: "coinSaved")
}
}
var coins: UInt { return _coins }
private init() {
_coins = UInt(UserDefaults.standard.integer(forKey: "coinSaved"))
}
func spend(coins: UInt) -> Bool {
guard _coins > coins else { return false }
_coins -= coins
return true
}
func store(coins: UInt) -> UInt {
_coins += coins
return _coins
}
}
As you can see, other objects can only read the coins property but they can't modify it directly, if they want to modify it they have to use either spend or store. spend makes sure you can't spend more coins than you have.

updating an SKLabel to show the right integer

ok so first off I have an achievement Variable that is defined from a struct like so:
struct Achieve {
var node: SKSpriteNode?
var aName:String = ""
var aDes:String = ""
var aImage: String = "" {
didSet {
node?.texture = SKTexture(imageNamed: aImage)
}
}
var aAmount:Int = 0
var aRequired:Int = 0
var aStage:Int = 0
}
var Ach1 = Achieve(node: nil, aName: "Player", aDes: "Games Played", aImage: "locked", aAmount: 0, aRequired: 10, aStage: 1)
My problem is when I try and change the number of the aAmount property it isn't displayed on the SKLabel that displays the amount it just stays at 0 my sklabel is defined in a function like below:
func generateLabels(location: CGPoint, page:SKSpriteNode, text: String) -> SKLabelNode {
let node = SKLabelNode()
node.fontColor = UIColor.whiteColor()
node.fontName = "Helvetica"
node.position = location
node.fontSize = 15
node.text = text
page.addChild(node)
return node
}
func menu() {
_ = generateLabels(CGPointMake(0, -285), page:page1ScrollView, text: "\(Ach1.aAmount) / \(Ach1.aRequired)")
}
It seems to work if I make changes to aAmount when I stop running it and then change it and then run the game again. it also seems to make changes to the aAmount property during gameplay but it doesn't seem to update the label for some reason during the gameplay. Can someone please tell me why it won't update?
Also i'm updating the aAmount property like so:
Ach1.aAmount += 1
print("\(Ach1.Amount)")
In your Achieve struct, add a property called label:
var label: SKLabelNode?
And change the aAmount property to look something like this:
var aAmount: Int = 0 {
didSet {
label?.text = "\(aAmount) / \(aRequired)"
}
}
Now when you generate the labels, replace this:
_ = generateLabels(CGPointMake(0, -285), page:page1ScrollView,
text: "\(Ach1.aAmount) / \(Ach1.aRequired)")
with this
var label = _ = generateLabels(CGPointMake(0, -285), page:page1ScrollView,
text: "\(Ach1.aAmount) / \(Ach1.aRequired)")
Ach1.label = label
Now when you change the aAmount property of Ach1, the label's text should change as well.
Suggestions:
Judging from this and your last question about sprite node images not updating, I think your programming skills are not mature enough. In the long term, you should learn the basics first. Try playing around with UIKit and read more others' code. See how other people do things. You will definitely learn a lot. In the short term though, you shouldn't make each of all these properties binds to a node. That will make Achieve too dependent. What you should do is make Achieve a subclass of SKSpriteNode and add those other sprite nodes and label nodes as sub nodes of the Achieve node.
If I read your code, there isn't any part where you update your SKLabelNode.
If you call the function :
func menu() {
_ = generateLabels(CGPointMake(0, -285), page:page1ScrollView, text: "\(Ach1.aAmount) / \(Ach1.aRequired)")
}
for example to didMoveToView, it generate a label with your aAmount value when you call your scene (so 1 time only as didMoveToView works).
Instead, assuming that you call this menu function many times, it generate everytime a new label but there is no code written from you where you remove the previous label or labels added in past (see page.addChild(node)) and I don't understand why you want to return a label from your function if you don't use it, in fact you assign your result to _. Also you don't explain what is "page1ScrollView", are you using a scrollview?
Anyway, the best mode to update your label is simply to create 1 time only (for example on didMoveToView) and, everytime you want to update it, doing:
myNodeLabel.text = "\(Ach1.aAmount) / \(Ach1.aRequired)"
instead of re-create it everytime. I hope this helps to understand the dynamics that prevent your label to upgrade.
You have to reassign the text of the SKLabel. So when you call the function to change the number (or whatever it is), also run the code to make the label what you want it to be. My guess as to why it only works sometimes is that sometimes you call it in the update (or somewhere else that gets called repeatedly) and sometimes you call it somewhere like didMoveToView (only called once, so label doesn't get updated because of the way your code is).
Just whenever you update aAmount, add this code after it:
label.text = "\(aAmount)"

NSUserDefault GlobalData/Base Scene Multiple "Keys" Overall Total

I have multiple scenes that are stored in Global/BaseScene
Each SceneType: is stored as enum: set to an Integer similar to this present scene
First objective was to get the score to populate in each scene, there is seven of them. DONE thanks to stack overflow, and experimentation
I had to create default keys for each individual scene, to calculate the highScore, so I have seven unique "highScore" "keys" for each scene.
In the GameScene:
var highScoreA1: Int = 0
var score: Int = 0 {
didSet {
a1Score.text = "Score: \(score)"
}
}
//above called before override func didMoveToView(view: SKView) {
//Called in GameOver Method
let scoreDefault = NSUserDefaults.standardUserDefaults()
scoreDefault.setInteger(score, forKey: "score")
if (score > highScoreA1){
let highScoreA1Default = NSUserDefaults.standardUserDefaults()
highScoreA1Default.setInteger(score, forKey: "highScoreA1")
//highscoreA1Default.synchronize()
There are six more keys similar to this.. My objective is to populate a "totalScoreKey" in two different scenes a Hud Scene and another scene (possibly game over scene)
I was thinking a function to add these keys together to populate the total score.
Taking into consideration all these scenes are subclasses (of Global BaseScene, and each scene has sub classes (for the nodes operation, probably not relevant yet thought it might be useful)
I have tried: Moving all score data into a Class and using NSCoding/NSObject the required init, and optional binding, became a serious pain, and honestly I am trying to keep things simple for version one.
import Foundation
class GameState {
var score:Int = 0
var highScore:Int()
var totalScore:Int()
class var sharedInstance: GameState {
struct Singleton {
static let instance = GameState()
}
return Singleton.instance
}
}
init() {
// Init
score.type = 0
highScore = 0
totalScore = 0
// Load game state
let defaults = NSUserDefaults.standardUserDefaults()
highScore = defaults.integerForKey("highScore")
}
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: "highScore")
defaults.setInteger(totalScore, forKey: "totalScore")
NSUserDefaults.standardUserDefaults().synchronize()
}
Various functions that have not worked they all default to zero, that is until I figure out how to retrieve data properly.
let total score = "totalScoreKey"
similar to this post exactly like this post actually except I had to do different configurations because of my on personal set up..
total Score example I tried to implement
outside of class and referring to that in the scene I needed to populate that data. NO Go defaults to zero.
How do I simply add the value of those keys together? For which I can display similar to the other scenes, I already have implemented.
later on down the road I may want to assign a key chain value, right now I am just trying to get it show up for posting in GameCenter. (which also has key "GameCenterHighScore")
Setting them all to the same key "highScore" does not work.... just to be clear, I tried multiple times. Thanks in advance.
EDIT
if I try to add all the defaults together to get the total, it throws the following error:
Expression was too complex to be solved in reasonable time; consider
breaking up the expression into distinct sub-expressions
[Swift compound arithmetic operation ERROR3

Making a counter app - Do I have to do something after saving to Core Data?

Trying my hand in Swift and creating my first "app". I'm essentially creating a cookie-clicker clone where you just click an image in the center of your screen repeatedly to increment your score.
I'm experiencing some issues where clicking the image once will increment my score by 1, but upon clicking it again, the score stays 1.
I'm almost positive I'm supposed to do something after saving to CoreData - I just don't know what. Do I have to refresh the View? Sorry for the newbie question
#IBAction func tapHomeImage(sender: AnyObject) {
//Score is the name of my Entity, with "points" being an attribute
let entity = NSEntityDescription.entityForName("Score", inManagedObjectContext: managedObjectContext!)
let score = Score(entity: entity!, insertIntoManagedObjectContext: managedObjectContext!)
// **As an aside question, is there a more efficient way to code the below? The issue I have is that score.points is of type NSNumber
let intScore = score.points as Int
let incrementedIntScore = intScore + 1
score.points = NSNumber(integer: incrementedIntScore)
var error: NSError?
managedObjectContext?.save(&error)
//homeScoreLabel is an IBOutlet for the Score displayed on the page
homeScoreLabel?.text = "\(score.points)"
}
Thank you very much!
You don't need to do anything after saving, you need to do something before calling this method. You need to find an instance of Score that already exists, and use that.
When you get to this line:
let score = Score(entity: entity!, insertIntoManagedObjectContext: managedObjectContext!)
You create a new instance of the Score entity, every time this method runs. So when you do this:
let intScore = score.points as Int
let incrementedIntScore = intScore + 1
score.points = NSNumber(integer: incrementedIntScore)
You're working with a brand new instance every time. The score is initially zero, and you always increase it to 1.
What you should do:
Use a single instance of Score and save that as a property of your view controller. That is, have something like this:
var score: Score?
Assign a value to this property once, when the view controller loads. Since you're saving data, you should use executeFetchRequest to look up a previously-saved instance. If no previous instance exists, then create a new one via insertIntoManagedObjectContext.
Use that instance in this IBAction.
There are other ways to fix the code. The key is to make sure that you use the same instance of Score every time instead of creating a new one at every tap.

Declaring a random amount of objects in Swift

I'm basically trying to do what this guy is trying to do, but with Swift:
Declaring a random amount of objects in c++
I'm pretty new to programming and very new to Swift so I'm a little fuzzy on how to do some things. Basically, I just want to declare a random amount of enemies that my player has to weave through and to register when he's hit one of them. I've been looking every where for an answer and either this is a really stupid question, so stupid no one has ever needed to put it on the internet, or Sift is too new for it to have been a problem for someone else. I'm guessing it's a stupid question, but regardless I'm out of ideas on how to figure this out.
Thanks.
Swift arrays are dynamic, at least the mutable type that are declared with var array.
So you can just add objects to your array until you have enough objects.
e.g.:
var objects = [String]()
//let numberOfObjects = arc4random_uniform(10) // between 0 and 9
let numberOfObjects = arc4random_uniform(5) + 5 // between 5 and 9
for i in 0..<numberOfObjects {
let object = String()
objects.append(object)
}
println("we have \(countElements(objects)) objects")
Maybe I'm too dense to understand the question, but it seems to me that simply declaring an NSMutableArray would do what you want:
let myArray:NSMutableArray = NSMutableArray()
You would have all the flexibility to add or remove objects as you please.
In any given loop, you could remove all objects, then use a random number generator to add back a random number of objects if that's what you're trying to do.
My additional comment on the other answer to this is that I would explicitly type cast everything in the statement that generates the random number:
var randomNumber:Int = Int(arc4random_uniform(UInt32(5)))
Here's an example using a simple Enemy class:
class Enemy : NSObject {
override init() {
super.init()
}
}
let range = (5, 20)
let numEnemies = Int(arc4random_uniform(UInt32(range.1 - range.0) + 1) + UInt32(range.0))
var enemyArray = [Enemy]()
for i in 0..<numEnemies {
enemyArray.append(Enemy())
}
You want a random number of objects in an array. Assume:
class MyObject {}
and that you have an upper bound on the number of objects:
let theObjectLimit = 10
then you can create a random number of new, distinct instances of MyObject with simply:
let theObjects = (1...arc4random_uniform(theObjectLimit)).map {
_ -> MyObject in return MyObject ()
}
When learning a language like Swift, that has first class functions (aka closures), you'll want to use functions like map and reduce wherever possible - especially instead of iteration.