I'm trying to report my highscore in the game center. I think my code is working but the game center is not updating with the highscore.
The leaderboard is create with this reference name : "funfairBalloon" and this leaderboard ID : 55009943.
I have 3 sandbox testers, the game center is enable and the players are authenticated in game center.
and my code to authenticate and to report is :
func authenticateLocalPlayer()
{
var localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler =
{ (viewController : UIViewController!, error : NSError!) -> Void in
if viewController != nil
{
self.presentViewController(viewController, animated:true, completion: nil)
}
else
{
if GKLocalPlayer.localPlayer().authenticated {
let gkScore = GKScore(leaderboardIdentifier: "55009943")
gkScore.value = Int64(highscore)
GKScore.reportScores([gkScore], withCompletionHandler: {(error) -> Void in
let alert = UIAlertView(title: "Success",
message: "Score updated",
delegate: self,
cancelButtonTitle: "Ok")
alert.show()
})
}
}
}
}
Do you have an idea?
It's best practice to add your app id to the leaderboard identifier. I had trouble not getting it to work before then. You may be having the same troubles. Make a test leaderboard named "com.whateverName.55009943" and update your code. See if that works like it did for me.
If you are using Test Flight for your sandbox testers make sure to add them on iTunes connect as well.
Finally, this link should help you troubleshoot why you're not seeing anyone show up on the leaderboard if you followed the above advice.
You can take a look at this logic in this github repo https://github.com/jocelynlih/SwiftGameBook/blob/master/PencilAdventure/PencilAdventure/ScoreManager.swift#L26
To report score you need to call pass the authenticateHandler closure function and in that if localPlayer is authenticated then report score.
var localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = {(viewController : UIViewController!, error : NSError!) -> Void in
if viewController != .None {
// Show view controller
} else {
if localPlayer.authenticated {
var scoreToReport = GKScore(leaderboardIdentifier: "Leaderboard\(level)", player: localPlayer)
scoreToReport.value = Int64(score)
GKScore.reportScores([scoreToReport], withCompletionHandler: nil)
} else {
// User not authenticated
}
}
}
Related
Bear with me here, as there is a lot to explain!
I am working on implementing a Game Center high score leaderboard into my game. I have looked around for examples of how to properly implement this code, but have come up short on finding much material. Therefore, I have tried implementing it myself based off information I found on apples documentation.
Long story short, I am getting success printed when I update my score, but no scores are actually being posted (or at-least no scores are showing up on the Game Center leaderboard when opened).
Before I show the code, one thing I have questioned is the fact that this game is still in development. In AppStoreConnect, the status of the leaderboard is "Not Live". Does this affect scores being posted?
Onto the code. I have created a GameCenter class which handles getting the leaderboards and posting scores to a specific leaderboard. I will post the code in whole, and will discuss below what is happening.
class Leaderboard
{
var id : String
var leaderboard : GKLeaderboard
var loaded : Bool
init()
{
self.id = ""
self.leaderboard = GKLeaderboard()
self.loaded = false
}
init(id: String, leaderboard: GKLeaderboard, loaded: Bool)
{
self.id = id
self.leaderboard = leaderboard
self.loaded = loaded
}
}
class GameCenter
{
static let shared = GameCenter()
private var player = GKLocalPlayer.local
private var leaderboards : [Leaderboard] = []
func authenticatePlayer()
{
player.authenticateHandler = { (vc, error) -> Void in
if let error = error
{
print(error.localizedDescription)
}
else if let vc = vc
{
if let viewController = UIApplication.shared.windows.first!.rootViewController
{
viewController.present(vc, animated: true)
{
self.loadLeaderboards()
}
}
}
else
{
self.loadLeaderboards()
}
}
}
func loadLeaderboards()
{
var leaderboardIDs : [String] = []
// Gather all of the leaderboard ids that we have
for leaderboard in GameCenterLeaderboards.allCases
{
leaderboardIDs.append(leaderboard.rawValue)
}
// Load the leaderboard for all of these ids and add to a new array
GKLeaderboard.loadLeaderboards(IDs: leaderboardIDs) { (loadedLeaderboards, error) in
if let error = error
{
print(error.localizedDescription)
return
}
if let loadedLeaderboards = loadedLeaderboards
{
print("\n--- Loaded Leaderboards ---")
for loadedBoard in loadedLeaderboards
{
let board = Leaderboard(id: loadedBoard.baseLeaderboardID, leaderboard: loadedBoard, loaded: true)
self.leaderboards.append(board)
print("ID: \(board.id)")
}
print("\n")
self.updateLocalHighScore()
}
}
}
func playerAuthenticated() -> Bool
{
return player.isAuthenticated
}
func submitScore(id: String)
{
if ( playerAuthenticated() )
{
let leaderboard = getLeaderboard(id: id)
if ( leaderboard.loaded )
{
print("Submitting score of \(AppSettings.shared.highScore!) for leaderboard \(leaderboard.id)")
leaderboard.leaderboard.submitScore(AppSettings.shared.highScore, context: -1, player: player) { (error) in
if let error = error
{
print(error.localizedDescription)
}
else
{
print("Successfully submitted score to leaderboard")
}
}
}
}
}
func getLeaderboard(id: String) -> Leaderboard
{
if let leaderboard = leaderboards.first(where: { $0.id == id } )
{
return leaderboard
}
return Leaderboard()
}
func updateLocalHighScore()
{
let leaderboard = getLeaderboard(id: GameCenterLeaderboards.HighScore.rawValue)
if ( leaderboard.loaded )
{
leaderboard.leaderboard.loadEntries(for: [player], timeScope: .allTime) { (playerEntry, otherEntries, error) in
if let error = error
{
print(error.localizedDescription)
return
}
if let score = playerEntry?.score
{
print("Player Score in leaderboard: \(score)")
if( score > AppSettings.shared.highScore )
{
AppSettings.shared.highScore = score
print("High Score Updated!")
}
else
{
// Lets post the local high score to game center
self.submitScore(id: leaderboard.id)
print("Local High Score Is Greating, requesting a submit!")
}
}
}
}
}
}
In a different GameScene, once the game is over, I request to post a new high score to Game Center with this line of code:
GameCenter.shared.submitScore(id: GameCenterLeaderboards.HighScore.rawValue)
The last thing I have questions about is the context when submitting a score. According to the documentation, this seems to just be metadata that GameCenter does not care about, but rather something the developer can use. Therefore, I think I can cross this off as causing the problem.
I believe I implemented this correctly, but for some reason, nothing is posting to the leaderboard. This was ALOT, but I wanted to make sure I got all my thoughts down.
Any help on why this is NOT posting would be awesome! Thanks so much!
Mark
I had the same problem - submitting scores to Game Center by using a new GameKit API available from iOS 14 was never actually saved on the Game Center side (even though there was no any error reported).
Solution which worked for me in the end was simply using a Type Method (with my leaderboardID):
class func submitScore(_ score: Int,
context: Int,
player: GKPlayer,
leaderboardIDs: [String],
completionHandler: #escaping (Error?) -> Void)
instead of the Instance Method counterpart (which apparently has some bug on Apple side):
func submitScore(_ score: Int,
context: Int,
player: GKPlayer,
completionHandler: #escaping (Error?) -> Void)
I was going crazy over this, so I hope this helps someone else.
First of all I'm really new to Firestore and its functionalities, so I apologize if some of this might feel obvious to others. These things are just not registering in my mind yet.
I'm trying to create a 2 person multiplayer game using the language Swift and Firestore as the backend. However I'm not to sure how to create this functionality of only allowing two players inside a single game at a given time. How would I go about restricting each game to only allowing two players inside one game? Would this be something I need to set up within the security and rules portion of Firestore? Or would I need to create this functionality within how I model my data?
My current setup for how I'm modeling the data includes creating a collection of "Games" where each "Game" has two documents for "player1" and "player2". Then, within each one of those players/documents I store the values of each players functionalities. But with this approach, I still haven't solved the issue of only allowing two players within a single "Game"/collection. How do I prevent a third player from entering the game? or how would I handle the situation when more than one person enters a game at the same time?
Thank you for any advice possible.
You can use Cloud Functions to assign players to a game, manage when a game is full and then start it. Take a look at this article on Medium, Building a multi-player board game with Firebase Firestore & Functions
This is the code I ended up creating in Swift using Firestore to create the 2 person multiplayer game.
import UIKit
import Firebase
class WaitForOpponentViewController: UIViewController
{
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
var battleRoomDocumentReference : DocumentReference!
var battleRoomListenerRegistration : ListenerRegistration!
override func viewDidLoad(){
super.viewDidLoad()
activityIndicator.hidesWhenStopped = true
activityIndicator.startAnimating()
Firestore.firestore().collection(BATTLEROOMS_Collection)
.whereField(BATTLEROOMFULL_FIELD, isEqualTo: false)
.whereField(NUMOFPLAYERS_FIELD, isLessThan: 2)
.limit(to: 1)
.getDocuments { (snapShot, error) in
if let error = error
{
print("There was a error while fetching the documents: \(error)")
}
else
{
guard let snap = snapShot else {return}
if(snap.documents.count > 0)
{
//Update the current battle room
for document in snap.documents
{
Firestore.firestore().collection(BATTLEROOMS_Collection)
.document(document.documentID)
.setData(
[
BATTLEROOMFULL_FIELD : true,
NUMOFPLAYERS_FIELD : 2, //Note: Player1Id is not changed because there is already a player1Id when this document is updated
PLAYER2ID_FIELD : Auth.auth().currentUser?.uid ?? "AnonymousNum2"
], options: SetOptions.merge(), completion: { (error) in
if let error = error
{
print("There was an error while adding the second player to the battle room document : \(error)")
}
self.addBattleRoomListener(battleRoomDocumentId: document.documentID)
})
}
}
else
{
//Create a new battle room
self.battleRoomDocumentReference = Firestore.firestore().collection(BATTLEROOMS_Collection)
.addDocument(data:
[
BATTLEROOMFULL_FIELD: false,
NUMOFPLAYERS_FIELD : 1,
PLAYER1ID_FIELD : Auth.auth().currentUser?.uid ?? "AnonymousNum1",
PLAYER2ID_FIELD : ""
], completion: { (error) in
if let error = error
{
print("Error while adding a new battle room/player 1 to the battle room document : \(error)")
}
})
self.addBattleRoomListener(battleRoomDocumentId: self.battleRoomDocumentReference.documentID)
}
}
}
}
override func viewWillDisappear(_ animated: Bool) {
//Remove Battle Room Listener
battleRoomListenerRegistration.remove()
activityIndicator.stopAnimating()
}
func addBattleRoomListener(battleRoomDocumentId : String)
{
battleRoomListenerRegistration = Firestore.firestore().collection(BATTLEROOMS_Collection)
.document(battleRoomDocumentId)
.addSnapshotListener { (documentSnapshot, error) in
guard let snapshot = documentSnapshot else { return }
guard let documentData = snapshot.data() else { return }
let battleRoomFullData = documentData[BATTLEROOMFULL_FIELD] as? Bool ?? false
let numOfPlayerData = documentData[NUMOFPLAYERS_FIELD] as? Int ?? 0
if(battleRoomFullData == true && numOfPlayerData == 2)
{
print("Two Players in the Game, HURRAY. Segue to GAME VIEW CONTROLLER")
}
else
{
return
}
}
}
#IBAction func cancelBattle(_ sender: UIButton) {
//NOTE: Canceling is only allowed for the first user thats creates the Battle Room, once the Second Person enters the Battle Room the system will automatically segue to the Game View Controller sending both players into the game VC
Firestore.firestore().collection(BATTLEROOMS_Collection)
.document(battleRoomDocumentReference.documentID)
.delete { (error) in
if let error = error
{
print("There was an error while trying to delete a document: \(error)")
}
}
activityIndicator.stopAnimating()
self.dismiss(animated: true, completion: nil)
}
}
My Game Center Authentication is not working. When I build and run, it won't show my username.. has signed in. Also, when I try to add my score I get a screen that says "no data availible". Heres my code.
override func viewDidLoad() {
super.viewDidLoad()
gcAuthPlayer()
}
#IBAction func GCButton(sender: AnyObject) {
saveHighScore(GameScene().highScoreNumer)
showLeaderBoard()
if GameScene().currentScore > GameScene().highScoreNumer{
saveHighScore(GameScene().currentScore)
}
}
func showLeaderBoard(){
let viewController = self.view.window?.rootViewController
let gcvc = GKGameCenterViewController()
gcvc.gameCenterDelegate = self
viewController?.presentViewController(gcvc, animated: true, completion: nil)
}
func saveHighScore(number: Int){
if GKLocalPlayer.localPlayer().authenticated{
let scoreReporter = GKScore(leaderboardIdentifier: "myleaderboard")
scoreReporter.value = Int64(number)
let scoreArray : [GKScore] = [scoreReporter]
GKScore.reportScores(scoreArray, withCompletionHandler: nil)
}
}
func gcAuthPlayer(){
let localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = {
(view, error) in
if view != nil{
self.presentViewController(view!, animated: true, completion: nil)
}else{
print(GKLocalPlayer.localPlayer().authenticated)
}
}
}
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController) {
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
This code makes no sense
saveHighScore(GameScene().highScoreNumer)
showLeaderBoard()
if GameScene().currentScore > GameScene().highScoreNumer{
saveHighScor
You are creating a new instance of GameScene everytime you try to update the score and therefore your score is nil
I would need to see some more code but for now you need to change the score property in your game scene. For example make it a static property so you can get it in other classes.
class GameScene: SKScene {
static var currentScore = 0
static var highscoreNumber = 0
}
Than in your Scenes or ViewController you can get it like so
GameScene.currentScore = 5
GameScene.highscoreNumber = 5
Just remember that you have to reset the score to 0 everytime you restart your gameScene because it a static property.
GameScene.currentScore = 0
GameScene.highscoreNumber = 0
Than your code to post the score should look like this
saveHighScore(GameScene.highScoreNumer)
showLeaderBoard()
if GameScene.currentScore > GameScene.highScoreNumer{
saveHighScor
Your score reporting code should also handle the error and actually do the completion handler. So change it to something like this.
/// Save leaderboard progress
func reportLeaderboardProgress(value: Int, leaderboardID: String) {
let scoreReporter = GKScore(leaderboardIdentifier: leaderboardID)
scoreReporter.value = Int64(value)
GKScore.reportScores([scoreReporter]) { error in // Trailing Closure syntax
if let error = error {
print(error.localizedDescription)
return
}
print("Reported leaderboard progress \(value) to leaderboardID \(leaderboardID)")
}
}
It is also a good idea to move that code into another class to keep your overall code cleaner and more reusable.
For a nice and simple example check this helper on gitHub.
https://github.com/jackcook/GCHelper
Let me know how it goes.
I beg all of thee. I searched everywhere but i can't find working swift 2.0 code for Facebook custom login button with returning Access Token. My code simply don't work. (token is not returning Facebook token)
Considering that you have done all the setup as described by the Facebook developer page, you need to do the following in your login view controller:
// Import the following on top below "import UIKit" in your login view controller
import FBSDKCoreKit
import FBSDKLoginKit
// This is your action code for your custom login button for login using facebook in Swift
#IBAction func fbLoginBtn(sender: AnyObject) {
let permisions = ["public_profile", "email"]
PFFacebookUtils.logInInBackgroundWithReadPermissions(permisions) {
(user: PFUser?, error: NSError?) -> Void in
if let error = error {
print(error)
} else {
if let user = user {
print(user)
// "yourSegue" below will be the segue identifier for your new view.
self.performSegueWithIdentifier("yourSegue", sender: self)
}
}
}
}
In case you have not setup your app at Facebook developer, please following the simple steps as mentioned in Facebook Developer Page.
1 - Add a normal button in your view then add the FB login class "FBSDKLoginButton"
2 - Declare your button in your ViewController
#IBOutlet weak var FBLoginButton: FBSDKLoginButton!
3 - Add in your ViewDidload methode
if (FBSDKAccessToken.currentAccessToken() != nil)
{
// User is already logged in, do work such as go to next view controller.
}
else
{
self.FBLoginButton.delegate = self
FBLoginButton.readPermissions = ["public_profile", "email", "user_friends"]
}
4-Add Delegate Login and logout button funcutions
func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!) {
loginButton.readPermissions = ["public_profile", "email", "user_friends"]
let fbAccessToken = FBSDKAccessToken.currentAccessToken().tokenString
FBSDKGraphRequest(graphPath: "me?fields=id,name,email,gender,first_name,last_name,middle_name,birthday&access_token=\(fbAccessToken)", parameters: nil).startWithCompletionHandler({ (connection, result, error) in
if ((error) != nil) {
// Process error
print("Error: \(error)")
} else {
print("fetched user: \(result)")
}
})
}
func loginButtonDidLogOut(loginButton: FBSDKLoginButton!) {
print("User Logged Out")
}
I am trying to authenticate the local player in my SPRITE-KIT/SWIFT game but it always returns false, and the GameCenter sign in bubble does not come up. Here's the function I am using:
//initiate gamecenter
func authenticateLocalPlayer(){
var localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = {(viewController, error) -> Void in
if (viewController != nil) {
//These 2 lines are the only parts that have been changed
let vc: UIViewController = self.view!.window!.rootViewController!
vc.presentViewController(viewController, animated: true, completion: nil)
}
else {
println((GKLocalPlayer.localPlayer().authenticated))
}
}
}
When i first used this code, it worked and the GameCenter sign in came up. I didn't change anything at all and it just stopped working for some reason and now always returns FALSE so the player doesn't get signed in. Help will be much appreciated. Thank You!!