GameCenter Integration with SpriteKit - ios

I'm simply trying to log a high score of a single game mode in a game I'm making. I have a leaderboard setup in Game Center on iTunes Connect.
So, my question is, how do I integrate this into my game? I've seen other solutions but can't seem to figure out how they fit into my project.
Thanks!

In your view controller implement the GKGameCenterControllerDelegate
Create a local player
var localPlayer: GKLocalPlayer = GKLocalPlayer.localPlayer()
Authenticate the player in the viewDidLoad with the game center and present the authentication success
localPlayer.authenticateHandler = {(ViewController, error) -> Void in
if((ViewController) != nil) {
self.presentViewController(ViewController, animated: true, completion: nil)
}
}
Report your score from anywhere in your game
if (GKLocalPlayer.localPlayer().authenticated) {
let gkScore = GKScore(leaderboardIdentifier: "YOUR-LEADERBOARD-ID")
gkScore.value = Int64(YOUR-SCORE)
GKScore.reportScores([gkScore], withCompletionHandler: ( { (error: NSError!) -> Void in
if (error != nil) {
// handle error
println("Error: " + error.localizedDescription);
} else {
println("Score reported: \(gkScore.value)")
}
}))
}

Related

GKTurnBasedEventListener could not be set to delegate of my ViewController?

In objC the syntax written by Rawendrich for GKTurnBasedEventListener, which was GKTurnBasedEventHandler there at that time, now changed by Apple is as below.
if (!gameCenterAvailable) return;
void (^setGKEventHandlerDelegate)(NSError *) = ^ (NSError *error)
{
GKTurnBasedEventHandler *ev =
[GKTurnBasedEventHandler sharedTurnBasedEventHandler];
ev.delegate = self;
};
NSLog(#"Authenticating local user...");
if ([GKLocalPlayer localPlayer].authenticated == NO) {
[[GKLocalPlayer localPlayer]
authenticateWithCompletionHandler:
setGKEventHandlerDelegate];
} else {
NSLog(#"Already authenticated!");
setGKEventHandlerDelegate(nil);
}
Now after converting this to swift, and with the composition of writing down GKTurnBasedEventListener instead of GKTurnBasedEventHandler, this comes the following way.
// Converted with Swiftify v1.0.6381 - https://objectivec2swift.com/
if !gameCenterAvailable {
return
}
var setGKEventHandlerDelegate: ((_: Error) -> Void)? = {(_ error: Error?) -> Void in
var ev = GKTurnBasedEventHandler.shared()
ev.delegate = self
}
print("Authenticating local user...")
if GKLocalPlayer.localPlayer().authenticated == false {
GKLocalPlayer.localPlayer().authenticate(withCompletionHandler: setGKEventHandlerDelegate)
}
else {
print("Already authenticated!")
setGKEventHandlerDelegate(nil)
}
Unfortunately this is not the right syntax to set delegate of GKTurnBasedEventListener for my ViewController.
Please if anyone of you could solve this for me, because without this I'm not able to read through event listener's default functions.
Cheers!
FYI, if you want a working example of how to use GKLocalPlayerListener during a turn-based GameKit match, you are welcome to take a look at this example project for a turn based game. I hope it helps to see all the pieces in context.
Finally after just about harsh 10 hours, I figured out this problem from Here. Although this syntax is in objC, but there's no problem of converting it to swift from Swiftify.
Although a bit later than the real time, but I'm now able to understand that setting delegate of GKTunBasedEventListener is not like the one we do for UITableViewControllerDelegate.
Here, one must have to first authenticate local player, then after you have to register local player's listener to the ViewController's delegate GKLocalPlayerListener.
One other thing I found on Apple's Documentation:
Do not implement GKChallengeListener, GKInviteEventListener, GKSavedGameListener, and GKTurnBasedEventListener directly; implement GKLocalPlayerListener instead. You can listen for and handle multiple events using GKLocalPlayerListener.
So then on I've implemented in the following way.
import GameKit
class ViewController: UIViewController, GKTurnBasedMatchmakerViewControllerDelegate,
GKLocalPlayerListener {
.....
func player(_ player: GKPlayer, receivedTurnEventFor match: GKTurnBasedMatch, didBecomeActive: Bool) {
print("#1")
print(player)
print("#2")
print(match)
print("#3")
print(didBecomeActive)
if match.status == GKTurnBasedMatchStatus.open
{
if GKLocalPlayer.localPlayer() == match.currentParticipant
{
if didBecomeActive
{
// Active now
}
else
{
// Active already
}
}
else
{
// It's someone's turn
if match.matchData != myMatch?.matchData
{
// Match Data being Updated by Someone
print(player.alias ?? "No Name:")
}
}
}
thirdTopLabel.text = match.matchID! + "\n" + didBecomeActive.description
}
....
Now in ViewDidLoad() function put the following code.
// In the ViewDidLoad function
if(!GKLocalPlayer.localPlayer().isAuthenticated)
{
authenticatePlayer { (auth) in
weak var weakSelf = self
weak var weakPlayer = GKLocalPlayer.localPlayer()
if(auth){
weakPlayer?.register(weakSelf!)
self.suthentication = true;
}
else{
print("failed in authentication")
self.suthentication = false;
}
}
}
else
{
// Already Authenticated
GKLocalPlayer.localPlayer().register(self)
localPlayer = GKLocalPlayer.localPlayer()
}
And finally your Authentication function should be like this.
// authenticate local player :: Just Authentication
func authenticatePlayer(completionHandler: #escaping (_ resultedPlaces: Bool) -> Void) {
localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler =
{ (viewController , error ) -> Void in
if viewController != nil
{
self.present(viewController!, animated:true, completion: nil)
}
else
{
if self.localPlayer.isAuthenticated
{
completionHandler(true);
}
else
{
completionHandler(false);
print("not able to authenticate fail")
self.gameCenterEnabled = false
if (error != nil)
{
print("\(error.debugDescription)")
}
else
{
print( "error is nil")
}
}
}
}
}
NOTE: GKLocalPlayerListener won't work on simulator.

Game Center Not Saving Scores

Game Center used to work perfectly a while ago for a different app that I made. However strange things are happening with my current one for about two weeks now.
Uploading and download report no error and I can see my score fine. But if I stop play my game and then 6 hours later come back to it, my score is no longer there in the game center. I mean, literally no score at all. Downloading from game center which works 6 hours ago now cannot retrieve my score.
Game center status is live. I can see my score and others in "manage score" section. However, I cannot see anything except my score in the leaderboard from GKGameCenterViewController.
As far as I'm concern, everything has been configured correctly. Game Center in app's capability is on.
Code for showing leaderboard:
func showLeaderBoard() {
if GKLocalPlayer.localPlayer().isAuthenticated == false {
self.present(authenticationViewController!, animated: true, completion: nil)
return
}
let gamecenter = GKGameCenterViewController()
gamecenter.gameCenterDelegate = self
gamecenter.viewState = .leaderboards
gamecenter.leaderboardIdentifier = leaderBoardID
present(gamecenter, animated: true, completion: nil)
}
Code for downloading score:
func downloadBestScoreFromGameCenter() {
if GKLocalPlayer.localPlayer().isAuthenticated == false {return}
print("Downloading Score...")
let leaderBoard = GKLeaderboard()
leaderBoard.identifier = leaderBoardID
leaderBoard.loadScores(completionHandler: {
[unowned self] (scores, error) in
print("Download Error: \(error)")
scores?.forEach({print("\($0.player?.displayName!) \($0.value)")})
if let localPlayerScore = leaderBoard.localPlayerScore?.value {
self.bestScore = Int(localPlayerScore)
self.saveGameForCurrentState()
} else {
self.bestScore = 0
}
})
}
And uploading
func uploadBestScoreToGameCenter() {
if GKLocalPlayer.localPlayer().isAuthenticated == false {return}
let scoreItem = GKScore(leaderboardIdentifier: leaderBoardID)
scoreItem.value = Int64(self.bestScore)
GKScore.report([scoreItem], withCompletionHandler: {
(error) in
if let error = error {
print(error)
} else {
print("Upload complete: \(scoreItem.value)")
}
})
}
Ok the leaderboard is now working today. I guess you just have to wait people.

Integrating GameCenter in Swift with NSNotification, using SpriteKit - ViewController issue

I've tried a whole bunch of ways to get Game Center working in my SpriteKit game. Unfortunately the way I've done it in the past using ObjC and ViewControllers don't work because I'm using SKScene/ a GameScene.
This is the swift version of the code (I think):
// MARK: Game Center Integration
//login and make available
func authenticateLocalPlayer(){
let localPlayer = GKLocalPlayer()
print(localPlayer)
localPlayer.authenticateHandler = {(viewController, error) -> Void in
if ((viewController) != nil) {
self.presentViewController(viewController!, animated: true, completion: nil)
}else{
print((GKLocalPlayer.localPlayer().authenticated))
}
}
}
//submit a score to leaderboard
func reportScoreToLeaderboard(thisScore:Int){
if GKLocalPlayer.localPlayer().authenticated {
let scoreReporter = GKScore(leaderboardIdentifier: "LeaderboardID")
scoreReporter.value = Int64(thisScore)
let scoreArray: [GKScore] = [scoreReporter]
GKScore.reportScores(scoreArray, withCompletionHandler: { (error: NSError?) -> Void in
if error != nil {
print(error!.localizedDescription)
} else {
print("Score submitted")
}
})
}
}
//show leaderboard (call from button or touch)
func showLeaderboard() {
let vc = self.view?.window?.rootViewController
let gc = GKGameCenterViewController()
gc.gameCenterDelegate = self
vc?.presentViewController(gc, animated: true, completion: nil)
}
//hides view when finished
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController){
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
Unfortunetely, no matter what I try, I either get this error:
Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior
... or it just crashes.
I've read that NSNotifications can be used? But how?
I'm guessing the best way is to set it all up in the GameViewController.swift and use NSNotifications to communicate with the RootViewController from the GameScene? I can't seem to find a tutorial or example though.
Use delegation when a view controller needs to change views, do not have the view itself change views, this could cause the view trying to deallocating while the new view is being presented, thus the error you are getting

How do I show the GameCenter's leaderboard from another UIViewController

I have two UIViewControllers, MainViewController and HighScoreViewController.
In the MainViewController (the initial view controller that the user sees upon using the app) I have all the methods to log into Game Center and save the high score to a leaderboard.
override func viewDidLoad() {
super.viewDidLoad()
authenticateLocalPlayer()
if totalHighScore > prevTotalHighScore {
saveHighScore("totalHighScore", score: totalHighScore)
prevTotalHighScore = totalHighScore
NSUserDefaults.standardUserDefaults().setObject(prevTotalHighScore, forKey: "prevtotalhighscore")
}
}
func authenticateLocalPlayer() {
let localPlayer: GKLocalPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = {(ViewController, error) -> Void in
if((ViewController) != nil) {
// 1 Show login if player is not logged in
self.presentViewController(ViewController!, animated: true, completion: nil)
}
else {
print("Authentication is \(GKLocalPlayer.localPlayer().authenticated)")
}
}
}
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController) {
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
func saveHighScore(identifier: String, score: Int) {
if GKLocalPlayer.localPlayer().authenticated {
let scoreReport = GKScore(leaderboardIdentifier: identifier)
scoreReport.value = Int64(score)
let scoreArray: [GKScore] = [scoreReport]
GKScore.reportScores(scoreArray, withCompletionHandler: { (error) -> Void in
if error != nil {
print(error)
}
else {
print("Posted score of \(score)")
}
})
}
}
First of all, is this the best way to implement as such?
Secondly, my HighScoreViewController has a button that says 'LEADERBOARD' where if the user taps on, Game Center leaderboard for my game would pop up. How do I go about implementing said button? I already have a button set up and and #IBAction method linked to it, but I have no idea what code to place in it since all of the main Game Center code is placed inside the MainViewController.
Once you're authenticated with Game Center, it doesn't matter which controller retrieves the leaderboard. You say you already have a method linked to your button. So, you'll just add the retrieval code there. Apple's GKLeaderBoard reference has an example in obj-c for downloading leaderboard data:
GKLeaderboard *leaderboardRequest = [[GKLeaderboard alloc] init];
if (leaderboardRequest != nil)
{
leaderboardRequest.playerScope = GKLeaderboardPlayerScopeGlobal;
leaderboardRequest.timeScope = GKLeaderboardTimeScopeToday;
leaderboardRequest.identifier = #" ~~ your leaderboard identifier goes here ~~ "
leaderboardRequest.range = NSMakeRange(1,10);
[leaderboardRequest loadScoresWithCompletionHandler: ^(NSArray *scores, NSError *error)
{
if (error != nil)
{
// Handle the error.
}
if (scores != nil)
{
// Process the score information.
}
}];
}

Swift - Game Center not available

I'm trying to implement Game Center in my Swift game. I have a menu view controller, where the user can press a "SCORES" button, which should take them to the Game Center view controller.
This is the code that runs in the menu vc, when the button is pressed:
var gcViewController: GKGameCenterViewController = GKGameCenterViewController()
gcViewController.gameCenterDelegate = self
gcViewController.viewState = GKGameCenterViewControllerState.Leaderboards
gcViewController.leaderboardIdentifier = "VHS"
self.presentViewController(gcViewController, animated: true, completion: nil)
I have code in the Game Center vc, but I don't think it gets a chance to run. The app stops execution after this code (no breakpoints or errors, just won't let me tap anything) and displays a pop up message that reads:
Game Center Unavailable
Player is not signed in
The only other response I get is in Xcode, where the following line is printed to the log:
2014-08-29 14:10:33.157 Valley[2291:304785] 17545849:_UIScreenEdgePanRecognizerEdgeSettings.edgeRegionSize=13.000000
I have no idea what this means or why Game Center is not working. Can anybody help??
Assuming that you've enabled Game Center in your app and also added a leaderboard in iTunes Connect then you need to authenticate your player before you can show GC. Also, be sure that you've created a test user in iTunes Connect that you can use to log in to Game Center when the prompt appears.
Your MenuViewController should authenticate the local player in viewDidLoad like so:
class MenuViewController: UIViewController,
GKGameCenterControllerDelegate
{
var leaderboardIdentifier: String? = nil
var gameCenterEnabled: Bool = false
override func viewDidLoad()
{
super.viewDidLoad()
//Your code that sets up your scene or other set up code
//HERE IS WHERE YOU AUTHENTICATE
authenticateLocalPlayer()
}
func authenticateLocalPlayer()
{
var localPlayer = getLocalPlayer() // see GKLocalPlayerHack.h
localPlayer.authenticateHandler =
{ (viewController : UIViewController!, error : NSError!) -> Void in
if viewController != nil
{
self.presentViewController(viewController, animated:true, completion: nil)
}
else
{
if localPlayer.authenticated
{
self.gameCenterEnabled = true
localPlayer.loadDefaultLeaderboardIdentifierWithCompletionHandler
{ (leaderboardIdentifier, error) -> Void in
if error != nil
{
print("error")
}
else
{
self.leaderboardIdentifier = leaderboardIdentifier
print("\(self.leaderboardIdentifier)") //in your example "VHS" should be returned
}
}
}
else
{
print("not able to authenticate fail")
self.gameCenterEnabled = false
if error
{
print("\(error.description)")
}
else
{
print( "error is nil")
}
}
}
}
}
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController!)
{
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
}
After you've successfully authenticated then you should be able to present Game Center.
Note the line:
var localPlayer = getLocalPlayer() // see GKLocalPlayerHack.h
To get that to work you need to do a little hack to get GKLocalPlayer to instantiate correctly in Swift.
Create a new class in Objective-C and name the file GKLocalPlayerHack.h/m
In the header put:
// GKLocalPlayerHack.h
// Issue with GameKit and Swift
// https://stackoverflow.com/questions/24045244/game-center-not-authenticating-using-swift
#import <GameKit/GameKit.h>
#interface GKLocalPlayerHack : NSObject
GKLocalPlayer *getLocalPlayer(void);
#end
In the implementation file put:
// GKLocalPlayerHack.m
// Issue with GameKit and Swift
// https://stackoverflow.com/questions/24045244/game-center-not-authenticating-using-swift
#import "GKLocalPlayerHack.h"
#implementation GKLocalPlayerHack
GKLocalPlayer *getLocalPlayer(void)
{
return [GKLocalPlayer localPlayer];
}
#end
Be sure to add:
#import "GKLocalPlayerHack.h"
To your bridging header.
Credit to #marmph for his answer in this question: Game Center not authenticating using Swift
I solved this problem for TEST MODE in this way:
Go to Game Center App Tab "Friends" click Setting at the end of the screen: SANDBOX and LOGGING MUST BE ON MODE
I hope that it works for everyone
You can use that, I create a Easy Game Center for iOS game center in github
https://github.com/DaRkD0G/Easy-Game-Center-Swift
Message from France, Merry Christmas
Begin
(1) Add FrameWork GameKit.framework
(2) Create two files :
GKLocalPlayerHack.h
#import <GameKit/GameKit.h>
#interface GKLocalPlayerHack : NSObject
GKLocalPlayer *getLocalPlayer(void);
#end
GKLocalPlayerHack.m
#import "GKLocalPlayerHack.h"
#implementation GKLocalPlayerHack
GKLocalPlayer *getLocalPlayer(void) {
return [GKLocalPlayer localPlayer];
}
#end
(3) In your Swift Bridging Header.h (Objectic-c import)
#import "GKLocalPlayerHack.h"
Next
class GameCenter {
// Game Center
let gameCenterPlayer=GKLocalPlayer.localPlayer()
var canUseGameCenter:Bool = false {
didSet{if canUseGameCenter == true {// load prev. achievments form Game Center
gameCenterLoadAchievements()}
}}
var gameCenterAchievements=[String:GKAchievement]()
/**
builder
*/
init(uiControlNow : UIViewController) {
// Do any additional setup after loading the view.
self.gameCenterPlayer.authenticateHandler={(var gameCenterVC:UIViewController!, var gameCenterError:NSError!) -> Void in
if gameCenterVC != nil {
//showAuthenticationDialogWhenReasonable: is an example method name. Create your own method that displays an authentication view when appropriate for your app.
//showAuthenticationDialogWhenReasonable(gameCenterVC!)
uiControlNow.presentViewController(gameCenterVC, animated: true, completion: { () -> Void in
// no idea
})
}
else if self.self.gameCenterPlayer.authenticated == true {
self.self.canUseGameCenter = true
} else {
self.canUseGameCenter = false
}
if gameCenterError != nil
{ println("Game Center error: \(gameCenterError)")}
}
}
/**
Load prev achievement granted to the player
*/
func gameCenterLoadAchievements(){
// load all prev. achievements for GameCenter for the user to progress can be added
var allAchievements=[GKAchievement]()
GKAchievement.loadAchievementsWithCompletionHandler({ (allAchievements, error:NSError!) -> Void in
if error != nil{
println("Game Center: could not load achievements, error: \(error)")
} else {
for anAchievement in allAchievements {
if let oneAchievement = anAchievement as? GKAchievement {
self.gameCenterAchievements[oneAchievement.identifier]=oneAchievement}
}
}
})
}
/**
Add progress to an achievement
:param: Progress achievement Double
:param: ID Achievement
*/
func gameCenterAddProgressToAnAchievement(progress:Double,achievementID:String) {
if canUseGameCenter == true { // only update progress if user opt-in to use Game Center
// lookup if prev progress is logged for this achievement = achievement is already know (and loaded) form Game Center for this user
var lookupAchievement:GKAchievement? = gameCenterAchievements[achievementID]
if let achievement = lookupAchievement {
// found the achievement with the given achievementID, check if it already 100% done
if achievement.percentComplete != 100 {
// set new progress
achievement.percentComplete = progress
if progress == 100.0 {achievement.showsCompletionBanner=true} // show banner only if achievement is fully granted (progress is 100%)
// try to report the progress to the Game Center
GKAchievement.reportAchievements([achievement], withCompletionHandler: {(var error:NSError!) -> Void in
if error != nil {
println("Couldn't save achievement (\(achievementID)) progress to \(progress) %")
}
})
}
else {// achievemnt already granted, nothing to do
println("DEBUG: Achievement (\(achievementID)) already granted")}
} else { // never added progress for this achievement, create achievement now, recall to add progress
println("No achievement with ID (\(achievementID)) was found, no progress for this one was recoreded yet. Create achievement now.")
gameCenterAchievements[achievementID] = GKAchievement(identifier: achievementID)
// recursive recall this func now that the achievement exist
gameCenterAddProgressToAnAchievement(progress, achievementID: achievementID)
}
}
}
/**
Remove One Achievements
:param: ID Achievement
*/
func resetAchievements(achievementID:String) {
var lookupAchievement:GKAchievement? = gameCenterAchievements[achievementID]
if let achievement = lookupAchievement {
GKAchievement.resetAchievementsWithCompletionHandler({ (var error:NSError!) -> Void in
if error != nil {
ToolKit.log("Couldn't Reset achievement (\(achievementID))")
} else {
ToolKit.log("Reset achievement (\(achievementID))")
}
})
} else {
println("No achievement with ID (\(achievementID)) was found, no progress for this one was recoreded yet. Create achievement now.")
gameCenterAchievements[achievementID] = GKAchievement(identifier: achievementID)
// recursive recall this func now that the achievement exist
self.resetAchievements(achievementID)
}
}
}

Resources