Building a Spritekit/Gamekit Leaderboard Within a Specific Scene

Building a SpriteKit/GameKit leaderboard within a specific scene

This answer partially carries off on where we left off in the comments, since you didn't post your entire code I can't tell exactly where your hangup was, but this is what I put together along with a separate guide (it's a slightly different version of the code you post):

Author of most of the code:

https://www.reddit.com/r/swift/comments/3q5owv/how_to_add_a_leaderboard_in_spritekit_and_swift_20/

GameViewController.swift:

import UIKit
import SpriteKit
import GameKit

class GameViewController: UIViewController {

func authenticateLocalPlayer() {
let localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = {(viewController, error) -> Void in

if (viewController != nil) {
self.presentViewController(viewController!, animated: true, completion: nil)
}
else {
print((GKLocalPlayer.localPlayer().authenticated))
}
}
}

override func viewDidLoad() {
super.viewDidLoad()

/////authentication//////
authenticateLocalPlayer()

//... The rest of the default code
}

//... The rest of the default code
}

GameScene.swift (or whichever scene you want to use GC):


import SpriteKit
import GameKit
import UIKit

// Global scope (I generally put these in a new file called Global.swift)
var score = 0


//sends the highest score to leaderboard
func saveHighscore(gameScore: Int) {
print ("You have a high score!")
print("\n Attempting to authenticating with GC...")

if GKLocalPlayer.localPlayer().authenticated {
print("\n Success! Sending highscore of \(score) to leaderboard")

//---------PUT YOUR ID HERE:
// |
// |
// V
let my_leaderboard_id = "YOUR_LEADERBOARD_ID"
let scoreReporter = GKScore(leaderboardIdentifier: my_leaderboard_id)

scoreReporter.value = Int64(gameScore)
let scoreArray: [GKScore] = [scoreReporter]

GKScore.reportScores(scoreArray, withCompletionHandler: {error -> Void in
if error != nil {
print("An error has occured:")
print("\n \(error) \n")
}
})
}
}

// Your scene:
class GameScene: SKScene, GKGameCenterControllerDelegate {

// Local scope variables (for this scene):

// Declare a new node, then initialize it
let call_gc_node = SKLabelNode(fontNamed:"Chalkduster")
let add_score_node = SKLabelNode(fontNamed: "Helvetica")


override func didMoveToView(view: SKView) {

// Give our GameCenter node some stuff
initGCNode: do {

// Set the name of the node (we will reference this later)
call_gc_node.name = "callGC"

// Default inits
call_gc_node.text = "Send your HighScore of \(score) into Game Center"
call_gc_node.fontSize = 25
call_gc_node.position = CGPoint(
x:CGRectGetMidX(self.frame),
y:CGRectGetMidY(self.frame))

// Self here is the instance (object) of our class, GameScene
// This adds it to our view
self.addChild(call_gc_node)
}

// Give our Add label some stuff
initADDLabel: do {

// Set the name of the node (we will reference this later)
add_score_node.name = "addGC"

// Basic inits
add_score_node.text = "ADD TO SCORE!"
add_score_node.fontSize = 25
add_score_node.position = call_gc_node.position

// Align our label some
add_score_node.runAction(SKAction.moveByX(0, y: 50, duration: 0.01))

// Add it to the view
self.addChild(add_score_node)
}

}


override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {

// Get the position of our click
let TPOINT = touch.locationInNode(self)

// Get the name (string) of the node that was touched
let
node_that_was_touched: String?
= nodeAtPoint(TPOINT).name


// Prepare for switch statement, when we unwrap the optional, we don't want nil
guard (node_that_was_touched != nil)
else { print("-> before switch: found nil--not entering Switch");
return
}


// Find out which node we clicked based on node.name?, then do stuff:
switch node_that_was_touched! {

case "callGC":
// We clicked the GC label:

GameOver: do {

print("GAME OVER!")

// If we have a high-score, send it to leaderboard:
overrideHighestScore(score)

// Reset our score (for the next playthrough)
score = 0

// Show us our stuff!
showLeader()
}

case "addGC":
// we clicked the Add label:

// Update our *current score*
score += 1


default: print("no matches found")
}

}

}


override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
call_gc_node.text = "Send your HighScore of \(score) into Game Center"

}


// Gamecenter
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController) {
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}

//shows leaderboard screen
func showLeader() {
let viewControllerVar = self.view?.window?.rootViewController
let gKGCViewController = GKGameCenterViewController()
gKGCViewController.gameCenterDelegate = self
viewControllerVar?.presentViewController(gKGCViewController, animated: true, completion: nil)
}

// Your "game over" function call
func overrideHighestScore(gameScore: Int) {
NSUserDefaults.standardUserDefaults().integerForKey("highscore")
if gameScore > NSUserDefaults.standardUserDefaults().integerForKey("highscore")
{
NSUserDefaults.standardUserDefaults().setInteger(gameScore, forKey: "highscore")
NSUserDefaults.standardUserDefaults().synchronize()

saveHighscore(gameScore)
}
}
}

Pay special attention to

29: let my_leaderboard_id = "YOUR_LEADERBOARD_ID"

I put some silly ASCII art in there to make sure you don't miss it. You have to put in your actual leaderboard ID from the GameCenter set-up.

You have to add the GameCenter library as well, and do the iTunes connect to actually see your highscores in the pop-up window.

I think your initial problems were with not understanding some of the back-end of how SpriteKit and even iOS views work (which is totally fine, because Apple makes jumping in and making stuff very easy). But, as you see, following guides / tutorials can be difficult since your implementation will vary.

Here is some good info to start with:

Diagram of what happens each frame in SK:

Sample Image


So you see, the SKScene is the class with all of the fun stuff like Nodes and Actions, and is where everything (important to you) happens. You can generate these scenes through the Editor, but then you probably need to make a new .swift file to go with it (as each scene can have its own logic).

The editor is just a 'shortcut' to initializing a bunch of stuff, and honestly, you can make complete games with little code (but you very quickly find out that you want more)

So in this code, where you declare GameScene or PauseScreen (which are basically just class declarations, that inherit from SKScene), you quickly find this line talking about something that ISNT a scene:

override func didMoveToView(view: SKView)
.. it's calling a SKView... what is that, and where did it come from?

(Read about SKView here, and look at its inheritance):


https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SKView/index.html#//apple_ref/occ/cl/SKView


We find this SKView declaration in the GameViewController file, (which is just a class), notice that it's the same as the regular iOS apps mostly, as it inherits UIViewController:

override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true

/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true

/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill

skView.presentScene(scene)
}

Again, that method is declared in GameViewController.swift, which is basically just this:
class GameViewController: UIViewController


So how does all of this relate to iOS apps and SpriteKit? Well, they are all mashed on top of each other:

IOS app anatomy:

anatomy

Basically, from right to left, you have the Window, which is (correct me if wrong) the AppDelegate, then the ViewController, then your View, which has all of the cool stuff in it (Storyboards sit inside of the View, just as SKScenes sit inside of the View.... Labels, Nodes, or Buttons, all sit inside of their respective classes ((the view)))

It's all a big sandwich of inheritance.


Check out the Apple websites for more info.

https://developer.apple.com/library/safari/documentation/UserExperience/Conceptual/MobileHIG/ContentViews.html#//apple_ref/doc/uid/TP40006556-CH13-SW1

https://developer.apple.com/spritekit/

https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SpriteKitFramework_Ref/

https://developer.apple.com/library/safari/documentation/UserExperience/Conceptual/MobileHIG/Anatomy.html

Basically, everything is an Class inherited from a class inherited from a class and so on, so on... It can get messy. You can also see these inheritances in Xcode by CMD+clicking on them, which will jump you to the source file.

Goodluck with your studies and adventures in SpriteKit :)

Displaying a Game Center Leaderboard in SpriteKit

Include the GKGameCenterControllerDelegate protocol within your class.

class ViewController: UIViewController, GKGameCenterControllerDelegate

This method dismisses the Game Center view when "Done" is tapped:

func gameCenterViewControllerDidFinish(gcViewController: GKGameCenterViewController!) {
self.dismissViewControllerAnimated(true, completion: nil)
}

This function includes the code that is needed to display the leaderboard:

func showLeaderboard() {

// declare the Game Center viewController
var gcViewController: GKGameCenterViewController = GKGameCenterViewController()
gcViewController.gameCenterDelegate = self

gcViewController.viewState = GKGameCenterViewControllerState.Leaderboards

// Remember to replace "Best Score" with your Leaderboard ID (which you have created in iTunes Connect)
gcViewController.leaderboardIdentifier = "Best_Score"
// Finally present the Game Center ViewController
self.showViewController(gcViewController, sender: self)
self.navigationController?.pushViewController(gcViewController, animated: true)
self.presentViewController(gcViewController, animated: true, completion: nil)
}

You can now trigger the function showLeaderboard by pressing a UIButton:

@IBAction func buttonShowLeaderboard(sender: AnyObject) {
showLeaderboard()
}

how do I implement GameCenter Leaderboard?

First you must authenticate the player:

 var localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = {(viewController : UIViewController!, error : NSError!) -> Void in
if ((viewController) != nil) {
self.presentViewController(viewController, animated: true, completion: nil)
}else{

println((GKLocalPlayer.localPlayer().authenticated))
}
}

Then you can open the leaderboard:

 func showLeaderboard() {


var gcViewController: GKGameCenterViewController = GKGameCenterViewController()
gcViewController.gameCenterDelegate = self

gcViewController.viewState = GKGameCenterViewControllerState.Leaderboards


gcViewController.leaderboardIdentifier = "YOUR_BOARD_NAME"

self.showViewController(gcViewController, sender: self)
self.navigationController?.pushViewController(gcViewController, animated: true)

}
func gameCenterViewControllerDidFinish(gcViewController: GKGameCenterViewController!)
{

self.dismissViewControllerAnimated(true, completion: nil)
}

I suggest doing these in your view controller. Authenticate the player on viewDidAppear though not viewDidLoad

Difference between GameViewController and SKScenes

Here is some good info to start with:

Diagram of what happens each frame in SK:

Sample Image


So you see, the SKScene is the class with all of the fun stuff like Nodes and Actions, and is where everything (important to you) happens. You can generate these scenes through the Editor, but then you probably need to make a new .swift file to go with it (as each scene can have its own logic).

The editor is just a 'shortcut' to initializing a bunch of stuff, and honestly, you can make complete games with little code (but you very quickly find out that you want more)

So in this code, where you declare GameScene or PauseScreen (which are basically just class declarations, that inherit from SKScene), you quickly find this line talking about something that ISNT a scene:

override func didMoveToView(view: SKView)
.. it's calling a SKView... what is that, and where did it come from?

(Read about SKView here, and look at its inheritance):


https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SKView/index.html#//apple_ref/occ/cl/SKView


We find this SKView declaration in the GameViewController file, (which is just a class), notice that it's the same as the regular iOS apps mostly, as it inherits UIViewController:

override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true

/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true

/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill

skView.presentScene(scene)
}

Again, that method is declared in GameViewController.swift, which is basically just this:
class GameViewController: UIViewController


So how does all of this relate to iOS apps and SpriteKit? Well, they are all mashed on top of each other:

IOS app anatomy:

anatomy

Basically, from right to left, you have the Window, which is (correct me if wrong) the AppDelegate, then the ViewController, then your View, which has all of the cool stuff in it (Storyboards sit inside of the View, just as SKScenes sit inside of the View.... Labels, Nodes, or Buttons, all sit inside of their respective classes ((the view)))

It's all a big sandwich of inheritance.


Check out the Apple websites for more info.

https://developer.apple.com/library/safari/documentation/UserExperience/Conceptual/MobileHIG/ContentViews.html#//apple_ref/doc/uid/TP40006556-CH13-SW1

https://developer.apple.com/spritekit/

https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SpriteKitFramework_Ref/

https://developer.apple.com/library/safari/documentation/UserExperience/Conceptual/MobileHIG/Anatomy.html

Basically, everything is an Class inherited from a class inherited from a class and so on, so on... It can get messy. You can also see these inheritances in Xcode by CMD+clicking on them, which will jump you to the source file.

Goodluck with your studies and adventures in SpriteKit :)

SpriteKit: How do you highlight a section of the scene, like in a tutorial?

One way of achieving this is by composing your desired 'cookie' with SKSpriteNodes, then creating a texture to render it 'as is' to a new SpriteNode, which will then blend as a whole with the scene.

In this simple example I've used a rectangle to highlight, but you could make that node any node type or image, and adjust the alpha value accordingly.

Draw your scene as normal, then add this code:

// dim the entire background of the scene to 70% darker
SKSpriteNode* background = [SKSpriteNode spriteNodeWithColor:[UIColor colorWithRed:0
green:0
blue:0
alpha:0.7]
size:self.frame.size];

// make a square of 100,100. This could be an image or shapenode rendered to a spritenode
// make the cut out only dim 20% - this is because no dim will look very harsh
SKSpriteNode* cutOut = [SKSpriteNode spriteNodeWithColor:[UIColor colorWithRed:0
green:0
blue:0
alpha:0.2]
size:CGSizeMake(100,100)];

// add the cut out to the background and make the blend mode replace the colors
cutOut.blendMode = SKBlendModeReplace;
[background addChild:cutOut];

// we now need to make a texture from this node, otherwise the cutout will replace the underlying
// background completely

SKTexture* newTexture = [self.view textureFromNode:background];
SKSpriteNode* newBackground = [SKSpriteNode spriteNodeWithTexture:newTexture];

// position our background over the entire scene by adjusting the anchor point (or position)
newBackground.anchorPoint = CGPointMake(0,0);
[self addChild:newBackground];

// if you have other items in the scene, you'll want to increaes the Z position to make it go ontop.
newBackground.zPosition = 5;

Game Center integration with Sprite Kit?

you can authenticate like this

[[GKLocalPlayer localPlayer] authenticateWithCompletionHandler:^(NSError *error) {
if (error == nil)
{
static_setEnable( true );

NSLog(@" Authenticate local player complete");

}
else
{
static_setEnable( false );
NSLog(@"Authenticate local player Error: %@", [error description]);
}
}];
}

Adding Game Center Leaderboard

you could create a cocoa touch class with the UIViewController Subclass. In the viewdidLoad you could use something like this:

if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "Level1") {
// Configure the scene
[...]

// Present the scene
view.presentScene(scene)
}

Don't forget to import GameKit.

Is it possible to do a running hi-score leaderboard in GameKit?

You can do this but you have to manage it yourself. GameKit is oriented around a single-game high score, not cumulative scores. Note that a player won't appear more than once on the leader board because GKLeaderboard will only report back the highest score in the time range you specified.

Tracking a cumulative score isn't that hard. Here's some code I'm using to do this. Get the GKLeaderboard with the ID that is tracking the score, and then fetch the high score for the local user. Then, add the new total to the current high score and then report the new total.

- (void)updateLeaderboardWithID:(NSString *)identifier score:(int64_t)score {
GKLeaderboard* leaderBoard= [[GKLeaderboard alloc] init];
leaderBoard.category = identifier;
leaderBoard.timeScope = GKLeaderboardTimeScopeAllTime;
leaderBoard.range = NSMakeRange(1, 1);

[leaderBoard loadScoresWithCompletionHandler: ^(NSArray *scores, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^(void) {
GKScore *localScore = leaderBoard.localPlayerScore;
int64_t newValue = localScore.value + score;
localScore = [[GKScore alloc] initWithCategory:identifier];
localScore.value = newValue;
[localScore reportScoreWithCompletionHandler:^(NSError *error){
// error handling goes here
}];
);
}];
}


Related Topics



Leave a reply



Submit