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.
Related
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
I'm trying to implement a simple scoring system into my game using this tutorial as a reference:
http://www.raywenderlich.com/87232/make-game-like-mega-jump-sprite-kit-swift-part-2
The problem is, if I try to implement as is, it crashes in the GameScene.swift on this line:
let another = whichNode as! GameObjectNode
Here are the main parts of the code where the player collects the coins. I can also invite you to my repo if you'd like to take a closer and better look. I know it can be hard from looking at the code I pasted up here.
GameObjectNode.swift:
enum CoinType: Int {
case Normal = 0
case Special
}
struct CollisionCategoryBitmask {
static let Player: UInt32 = 0x00
static let Coin: UInt32 = 0x01
static let Platform: UInt32 = 0x02
}
class GameObjectNode: SKNode {
func collisionWithPlayer(player: SKNode) -> Bool {
return false
}
func checkNodeRemoval(playerY: CGFloat) {
if playerY > self.position.y + 300.0 {
self.removeFromParent()
}
}
}
class CoinNode: GameObjectNode {
let coinSound = SKAction.playSoundFileNamed("StarPing.wav", waitForCompletion: false)
var coinType: CoinType!
override func collisionWithPlayer(player: SKNode) -> Bool {
// Boost the player up
player.physicsBody?.velocity = CGVector(dx: player.physicsBody!.velocity.dx, dy: 400.0)
// Play sound
runAction(coinSound, completion: {
// Remove this Star
self.removeFromParent()
})
// Award score
GameState.sharedInstance.score += (coinType == .Normal ? 20 : 100)
// Award stars
GameState.sharedInstance.coins += (coinType == .Normal ? 1 : 5)
// The HUD needs updating to show the new stars and score
return true
}
}
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)
// 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
}
}
And the GameScene.swift:
import SpriteKit
import CoreMotion
import GameplayKit
struct PhysicsCategory {
static let None: UInt32 = 0
static let Player: UInt32 = 0b1 // 1
static let PlatformNormal: UInt32 = 0b10 // 2
static let PlatformBreakable: UInt32 = 0b100 // 4
static let CoinNormal: UInt32 = 0b1000 // 8
static let CoinSpecial: UInt32 = 0b10000 // 16
static let Edges: UInt32 = 0b100000 // 32
}
class GameScene: SKScene, SKPhysicsContactDelegate {
// Other Properties
...
var player: SKSpriteNode!
// 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
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)
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
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
lblScore.text = "0"
hudNode.addChild(lblScore)
}
func setupNodes() {
...
player = fgNode.childNodeWithName("Player") as! SKSpriteNode
}
func createStarAtPosition(position: CGPoint, ofType type: CoinType) -> CoinNode {
// 1
let node = CoinNode()
let thePosition = CGPoint(x: position.x * scaleFactor, y: position.y)
node.position = thePosition
node.name = "NODE_COIN"
// 2
node.coinType = type
var sprite: SKSpriteNode
if type == .Special {
sprite = SKSpriteNode(imageNamed: "CoinSpecial")
} else {
sprite = SKSpriteNode(imageNamed: "Coin")
}
node.addChild(sprite)
// 3
node.physicsBody = SKPhysicsBody(circleOfRadius: sprite.size.width / 2)
// 4
node.physicsBody?.dynamic = false
node.physicsBody?.categoryBitMask = CollisionCategoryBitmask.Coin
node.physicsBody?.collisionBitMask = 0
node.physicsBody?.contactTestBitMask = 0
return node
}
func didBeginContact(contact: SKPhysicsContact) {
let other = contact.bodyA.categoryBitMask == PhysicsCategory.Player ? contact.bodyB : contact.bodyA
var updateHUD = false
let whichNode = (contact.bodyA.node != player) ? contact.bodyA.node : contact.bodyB.node
// Code crashes here
let another = whichNode as! GameObjectNode
updateHUD = another.collisionWithPlayer(player)
if updateHUD {
lblCoins.text = String(format: "X %d", GameState.sharedInstance.coins)
lblScore.text = String(format: "%d", GameState.sharedInstance.score)
}
switch other.categoryBitMask {
case PhysicsCategory.CoinNormal:
if let coin = other.node as? SKSpriteNode {
emitParticles("CollectNormal", sprite: coin)
jumpPlayer()
runAction(soundCoin)
}
case PhysicsCategory.CoinSpecial:
if let coin = other.node as? SKSpriteNode {
emitParticles("CollectSpecial", sprite: coin)
boostPlayer()
runAction(soundBoost)
}
case PhysicsCategory.PlatformNormal:
if let platform = other.node as? SKSpriteNode {
if player.physicsBody!.velocity.dy < 0 {
platformAction(platform, breakable: false)
jumpPlayer()
runAction(soundJump)
}
}
case PhysicsCategory.PlatformBreakable:
if let platform = other.node as? SKSpriteNode {
if player.physicsBody!.velocity.dy < 0 {
platformAction(platform, breakable: true)
jumpPlayer()
runAction(soundBrick)
}
}
default:
break;
}
}
I dont understand the code you use in didBeganContact, but you can define the contact bodies in this way:
enum Ctg:UInt32
{
case Coin = 1
case Hero = 2
case Villain = 4
case Car = 8
}
var hero = SKSpriteNode()
hero.physicsBody = SKPhysicsBody(rectangleOfSize: hero.size)
hero.physicsBody?.categoryBitMask = Ctg.Hero.rawValue
hero.physicsBody?.contactTestBitMask = Ctg.Coin.rawValue | Ctg.Villian.rawValue
var coin = SKSpriteNode()
coin.physicsBody = SKPhysicsBody(rectangleOfSize: coin.size)
coin.physicsBody?.categoryBitMask = Ctg.Coin.rawValue
coin.physicsBody?.contactTestBitMask = Ctg.Hero.rawValue
func didBeginContact(contact: SKPhysicsContact)
{
var first = SKNode()
var sec = SKNode()
// this way you ensure that the first body is the most valuable Ctg (enum)
if contact.bodyA.node?.physicsBody?.categoryBitMask > contact.bodyB.node?.physicsBody?.categoryBitMask
{
first = contact.bodyA.node!
sec = contact.bodyB.node!
}
else
{
first = contact.bodyB.node!
sec = contact.bodyA.node!
}
// this part be sure that the category of first it is of most value that sec
if first.physicsBody!.categoryBitMask == Ctg.Hero.rawValue && sec.physicsBody!.categoryBitMask == Ctg.Coin.rawValue
{
hero.coins++
scene.labelCoins.text = String(coins)
}
if first.physicsBody!.categoryBitMask == Ctg.Villain.rawValue && sec.physicsBody!.categoryBitMask == Ctg.Hero.rawValue
{
gameOver = true
hero.removeFromParent()
lostGame()
}
}
I'm making some great progress with my first iOS game app, currently I'm working on the high score element and if anyone would be so kind as to help guide me how to use the correct code.
I've followed a few tutorials with mixed results, so far I'm increasing the score count by 1 for each screen press (will be changed but using for the purpose for this post), this works fine however I need some help saving both the high score using NSUserDefaults and also displaying the high score.
Below is the extract of my code, currently the score works fine however the high score remains at 0 and doesn't increase. Any help would be appreciated!
Here is my code.....
Thank you.
EDITED WITH REWORKED CODE
// in gameScene:
var scoreLabelNode = SKLabelNode()
var score = NSInteger()
var highScoreLabelNode = SKLabelNode()
var highScore = NSInteger()
// in did move to view
score = 0
scoreLabelNode.fontName = "Helvetica-Bold"
scoreLabelNode.position = CGPoint(x: self.frame.size.width / 2.8, y: self.frame.size.height / 1.2 )
scoreLabelNode.fontSize = 20
scoreLabelNode.alpha = 0.2
scoreLabelNode.zPosition = -30
scoreLabelNode.text = "Score \(score)"
self.addChild(scoreLabelNode)
// start of high score
var highScoreDefault = NSUserDefaults.standardUserDefaults()
if (highScoreDefault.valueForKey("highScore") != nil){
highScore = highScoreDefault.valueForKey("highscore") as! NSInteger!
highScoreLabelNode.text = NSString(format: "highscore : %i", highScore) as String
}
// highScore = 0
// if (score > highScore) {
// highScore = score
// highScoreLabelNode.text = NSString(format: "highscore : %i", highScore) as String
// }
highScoreLabelNode.fontName = "Helvetica-Bold"
highScoreLabelNode.position = CGPoint(x: self.frame.size.width / 2.6, y: self.frame.size.height / 1.3 )
highScoreLabelNode.fontSize = 20
highScoreLabelNode.alpha = 0.2
highScoreLabelNode.zPosition = -30
highScoreLabelNode.text = "High Score \(score)"
self.addChild(highScoreLabelNode)
// in touches began
let incrementScore = SKAction.runBlock ({
++self.score
self.scoreLabelNode.text = "Score \(self.score)"
})
self.runAction(incrementScore)
// in update before each frame
// highScore = 0
if (score > highScore) {
highScore = score
highScoreLabelNode.text = NSString(format: "highscore : %i", highScore) as String
}
To me it seems you dont ever call
if (score > highScore) {
highScore = score
highScoreLabelNode.text = NSString(format: "highscore : %i", highScore) as String
}
again anywhere, which is where your highscore is set? Is it called somewhere that is not indicated in the code you have provided?
From what I can see you are not saving the highscore to User Defaults with NSUserDefaults.standardUserDefaults.setObject(), also, I'm not sure why you are setting highScore to 0
Im trying to keep track of how many times the user loses in my game. So for every loss it goes up by 1. I also want to save it too so the user could see how many total times they lost. Right now the code I have it works the first time and goes to one but if I lose in the game after that it just stays at 1. What am I doing wrong? Thanks!
class level1: SKScene, SKPhysicsContactDelegate, GKGameCenterControllerDelegate {
var deathScore = 0
override func didMoveToView(view: SKView) {
var deathLabel = SKLabelNode()
deathLabel = SKLabelNode(fontNamed: "LadyIce-3D")
deathLabel.text = "100"
deathLabel.zPosition = 14
deathLabel.fontSize = 100
deathLabel.fontColor = SKColor.darkTextColor()
deathLabel.position = CGPointMake(self.size.width / 1.1, self.size.height / 1.4)
deathLabel.hidden = true
self.addChild(deathLabel)
}
if firstBody.categoryBitMask == HeroCategory && fourthBody.categoryBitMask == GameOverCategory {
deathScore++
deathLabel.hidden = false
let defaults = NSUserDefaults()
let saveDeaths = NSUserDefaults().integerForKey("saveNumberOfDeaths")
if(deathScore > saveDeaths)
{
NSUserDefaults().setInteger(saveDeaths, forKey: "saveNumberOfDeaths")
}
var showNumberOfDeaths = defaults.integerForKey("saveNumberOfDeaths")
deathLabel.text = String(showNumberOfDeaths)
}
}
You are declaring a new var deathScore everytime, initialized with 0 and incrementing it. It will always be 1.
UserDefaults.standard.set(UserDefaults.standard.integer(forKey: "saveNumberOfDeaths")+1, forKey: "saveNumberOfDeaths")
deathLabel.text = String(UserDefaults.standard.integer(forKey: "saveNumberOfDeaths"))
I have set a high score using NSUserDefaults where if the object makes contact with a block, the game ends. If the score is greater than the high score, it updates the high score with the new value.
How would I reset the high score back to zero with a button?
func didBeginContact(contact: SKPhysicsContact) {
if moving.speed > 0 {
if ( contact.bodyA.categoryBitMask & scoreCategory ) == scoreCategory || ( contact.bodyB.categoryBitMask & scoreCategory ) == scoreCategory {
// Balloon has contact with score entity
score++
scoreLabelNode.text = String(score)
// Add a little visual feedback for the score increment
scoreLabelNode.runAction(SKAction.sequence([SKAction.scaleTo(1.5, duration:NSTimeInterval(0.1)), SKAction.scaleTo(1.0, duration:NSTimeInterval(0.1))]))
} else {
moving.speed = 0
balloon.physicsBody?.collisionBitMask = blockCategory
NSUserDefaults.standardUserDefaults().integerForKey("highscore")
//Check if score is higher than NSUserDefaults stored value and change NSUserDefaults stored value if it's true
if score > NSUserDefaults.standardUserDefaults().integerForKey("highscore")
{
NSUserDefaults.standardUserDefaults().setInteger(score, forKey: "highscore")
NSUserDefaults.standardUserDefaults().synchronize()
}
NSUserDefaults.standardUserDefaults().integerForKey("highscore")
var highscoreShow = defaults.integerForKey("highscore")
highscoreLabelNode = SKLabelNode(fontNamed: "MarkerFelt-Wide")
highscoreLabelNode.position = CGPointMake(CGRectGetMidX(self.frame), 530)
highscoreLabelNode.zPosition = 10
highscoreLabelNode.fontSize = 18
highscoreLabelNode.text = "Highscore: \(highscoreShow)"
self.addChild(highscoreLabelNode)
gameoverLabelNode = SKLabelNode(fontNamed: "MarkerFelt-Wide")
gameoverLabelNode.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
gameoverLabelNode.zPosition = 10
gameoverLabelNode.text = "GameOver"
self.addChild(gameoverLabelNode)
returnBtn.position = CGPointMake(CGRectGetMidX(self.frame), 250)
returnBtn.zPosition = 5
returnBtn.setScale(0.5)
self.addChild(returnBtn)
println("end the game")
self.canRestart = true
}
}
}
To reset the high score back to zero all you need to use is:
//Sets the integer value for the key "highscore" to be equal to 0
NSUserDefaults.standardUserDefaults().setInteger(0, forKey: "highscore")
//Synchronizes the NSUserDefaults
NSUserDefaults.standardUserDefaults().synchronize()