I am having some problems trying to implement game center achievements into an iOS game. The game already has the player authentication and leaderboards setup and they are working fine. The first error I get is when using this method:
GKAchievementDescription.loadAchievementDescriptionsWithCompletionHandler
It gives me an error which states two things:
"The requested operation could not be completed due to an error communicating with the server."
"App does not support achievements."
Five achievements have been added to iTunes connect so I don't no why it says it doesn't support them. The other issues are when I use this method:
GKAchievement.reportAchievements
When this is called the error in the completion handler is nil, but it returns "no bundle for bundleID: (null)". The achievement banner doesn't show and there is no achievements tab in the game centre view.
The app was recently transferred to another developer but he then wanted some extra features added to it, so I'm using a provisioning profile and developer certificate provided by him so I can test game center and in app purchases properly. It seems like the problem I'm having is relating to the transfer?
So my question is what could be the problem causing the game to 'not support achievements'?
Any help would be much appreciated,
thank you.
Your question is a bit vague, how are those two methods looking exactly in your code. Its hard to help with two lines of a function
You load code should look something like this
/// Load achievements
func loadAchievements(){
print("Loading submitted achievements")
GKAchievement.loadAchievementsWithCompletionHandler( { (achievements, error:NSError?) -> Void in
guard error == nil else {
print("Error loading achievements progress: \(error)")
return
}
guard let validAchievements = achievements else { return }
for achievement in validAchievements {
print("Name: \(achievement.identifier)")
print("Percentage: \(achievement.percentComplete)")
print("Date: \(achievement.lastReportedDate)")
self.achievementsDict.updateValue(achievement, forKey: achievement.identifier!)
}
})
}
You report code should look something like this
/// Save achievement progress
func reportAchievementProgress(percent: Double, achievementID: String) {
guard self.localPlayer.authenticated else { return }
let achievement = self.checkAchievement(achievementID) as GKAchievement? //
if achievement != nil {
achievement!.percentComplete = percent
achievement!.showsCompletionBanner = true
GKAchievement.reportAchievements([achievement!], withCompletionHandler: { (error:NSError?) -> Void in
guard error == nil else {
print(error)
return
}
print("Reported achievement: \(achievementID)) to: \(percent) %")
})
}
}
/// Check achievement
private func checkAchievement(achievementID: String) -> GKAchievement {
var achievement = self.achievementsDict[achievementID]
if achievement == nil {
print("Achievement with no previous progress, saving...")
achievement = GKAchievement(identifier: achievementID)
self.achievementsDict.updateValue(achievement!, forKey: achievement!.identifier!)
}
return achievement!
}
achievementsDict is a dictionary where you cache your achievements.
var achievementsDict = [String: GKAchievement]()
Does this help?
Related
I have an iOS game with one Game Center leaderboard. Recently I published the game and it works fine with no issues. Then I did light version of the game and I'd like to use the same leaderboard for both games. I combined both versions of the game into the Game Center group and modified leaderboard ID, because Apple requires to start group leaderboard names with grp..
Now, if I load scores I receive nil. But if I firstly submit some score and load after that I receive only score for the local player. I checked the leaderboard on itunesconnect and I know for sure that there are a lot of records in the leaderboard. The leaderboard is the same as it was before combining into the group. I thought that Game Center needs some time to update, but I've waited about one day still see no changing.
So my question is why I received nil or score only for local player? Do I do something wrong? Or this is just a Game Center bug?
I found some similar issues here but the most recent one was posted about two years ago. Does anyone have any ideas? Any help appreciated!
Here is my code to load scores:
func loadScores(){
let leaderboard = GKLeaderboard()
leaderboard.identifier = "grp.myLeaderboardID"
leaderboard.loadScores { (scores, error) in
if error != nil {
print(error!.localizedDescription)
} else {
//do something with the scores
}
}
}
}
and to sumbit scores:
func submitScore(value: Int64) {
let leaderboardID = "grp.myLeaderboardID"
let sRating = GKScore(leaderboardIdentifier: leaderboardID)
sRating.value = value
GKScore.report([sRating], withCompletionHandler: { (error: Error?) -> Void in
if error != nil {
print(error!.localizedDescription)
}
})
}
UPDATE.
The problem has gone suddenly. I think that the issue has been dealt with apple server bug but with combining into a group
I would like to implement a reward interstitial in my game, but i'm getting a lot of AdColony errors such as: "No fill for ad request" or that my Zone ID is invalid.
To start of, this would be how I configured my AdColony Zone:
Zone is active? Yes
Zone Type: Preroll/Interstitial (Gives me "No fill for ad request error")
Value Exchange/V4VC (Gives me "Zone ID invalid, please check config error")
House Ads: Back Fill
Options: 0 0 1
Development: Show Test Ads Only (Although my app is currently Live)
The example they give you with the SDK download, is for Apps not for Games so I tried to kinda translate it for Games, although it wasn't that different, but there might be a problem with my current code.
So this is how I have it in my GameViewController.swift.
// Outside I declare a struct
struct Constants
{
static let adColonyAppID = "app5aab6bb6aaf3xxxxxx"
static let adColonyZoneID = "vz19959f95bd62xxxxxx"
static let currencyBalance = "coinAmount"
}
// Inside GameViewController
var ad: AdColonyInterstitial?!
var spinner: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
self.setupAdRewardBanner()
}
func setupAdRewardBanner() {
AdColony.configureWithAppID(Constants.adColonyAppID, zoneIDs: [Constants.adColonyZoneID], options: nil,
completion: {(zones) in
let zone = zones.first
zone?.setReward({ (success, name, amount) in
if (success) {
let storage = NSUserDefaults.standardUserDefaults()
let wrappedBalance = storage.objectForKey(Constants.currencyBalance)
var balance = 0
if let nonNilNumWrappedBalance = wrappedBalance as? NSNumber {
balance = Int(nonNilNumWrappedBalance.integerValue)
}
balance = balance + Int(amount)
let newBalance: NSNumber = NSNumber(integerLiteral: balance)
storage.setValue(newBalance, forKey: Constants.currencyBalance)
storage.synchronize()
self.updateCurrencyBalance()
}
})
//If the application has been inactive for a while, our ad might have expired so let's add a check for a nil ad object
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameViewController.onBecameActive), name: "onBecameActive", object: nil)
//AdColony has finished configuring, so let's request an interstitial ad
self.requestInterstitial()
})
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameViewController.triggerAdReward), name: "triggerAdReward", object: nil)
self.updateCurrencyBalance()
}
func requestInterstitial()
{
//Request an interstitial ad from AdColony
AdColony.requestInterstitialInZone(Constants.adColonyZoneID, options:nil,
//Handler for successful ad requests
success:{(newAd) in
//Once the ad has finished, set the loading state and request a new interstitial
newAd.setClose({
self.requestInterstitial()
})
//Interstitials can expire, so we need to handle that event also
newAd.setExpire( {
self.ad = nil
self.requestInterstitial()
})
//Store a reference to the returned interstitial object
self.ad = newAd
},
//Handler for failed ad requests
failure:{(error) in
NSLog("SAMPLE_APP: Request failed with error: " + error.localizedDescription + " and suggestion: " + error.localizedRecoverySuggestion!)
}
)
}
func triggerAdReward(sender: AnyObject)
{
if let ad = self.ad {
if (!ad!.expired) {
ad?.showWithPresentingViewController(self)
}
}
}
func updateCurrencyBalance()
{
//Get currency balance from persistent storage and display it
let storage = NSUserDefaults.standardUserDefaults()
let wrappedBalance = storage.objectForKey(Constants.currencyBalance)
var balance: Int = 0
if let nonNilNumWrappedBalance = wrappedBalance as? NSNumber {
balance = Int(nonNilNumWrappedBalance.integerValue)
}
print("current balance ", balance)
//XXX Run animation of giving user coins and update view
}
func onBecameActive()
{
//If our ad has expired, request a new interstitial
if (self.ad == nil) {
self.requestInterstitial()
}
}
And then after all that, I call this notification to request the ad interstitial when pressing a button after the user loses in GameScene.
NSNotificationCenter.defaultCenter().postNotificationName("triggerAdReward", object: nil)
I tried debugging, I can't seem to see the code getting inside the if (success) block. So there might be an issue there.
Does anyone know what I'm doing wrong?
After debugging more, i noticed that it's not advancing with this method
self.requestInterstitial()
So there might be an issue with my account maybe? Why is not passing through the success and goes through the error block?
The error in the console is :
SAMPLE_APP: Request failed with error: No fill for ad request and
suggestion: Make sure you have configured your zone properly in the
control panel: http://clients.adcolony.com.
Thanks in advance.
It seems your code should be working.
Since you want to implement a reward interstitial, you should set the zone type to V4VC.
In case it said "Zone ID invalid, please check config error", you should check twice the App id and zone id in the source code and the Adcolony client panel page.
After you changed the zone type, wait for some time(10 min?) to test, the server should need time to sync the status.
Test on device instead of simulator if possible.
Here is the document for v4vc: https://github.com/AdColony/AdColony-iOS-SDK-3/wiki/Rewarded-Interstitial-Ads-Quick-Start-Guide
I was trying to add achievements to one of my games, I made a list and started adding them with image and description in itunes connect, but I can't find a tutorial written in swift, and with a simple function like unlockachievement and the achievement id passed inside that
In my game, inside the viewdidload of the game over screen i made this
if (achievement condition){
//unlock achievement ("achievementID")
}
Is this a correct way to add achievements?In this view i have many stats passed from previous view so if I put the unlockachievement function here the popup should appear at the end of the game without distracting the player
For the leaderboards I made a func that gets score and leaderboard, I want to do the same for achievements, how do I do it?Is it possible?
I also read about achievement progress in the game center view,is it possible to avoid it?Especially with hidden achievements that should show up only when conditions are met
this is how I solved
func loadAchievementPercentages() {
print("get % ach")
GKAchievement.loadAchievementsWithCompletionHandler { (allAchievements, error) -> Void in
if error != nil {
print("GC could not load ach, error:\(error)")
}
else
{
//nil if no progress on any achiement
if(allAchievements != nil) {
for theAchiement in allAchievements! {
if let singleAchievement:GKAchievement = theAchiement {
self.gameCenterAchievements[singleAchievement.identifier!] = singleAchievement
}
}
}
for(id,achievement) in self.gameCenterAchievements {
print("\(id) - \(achievement.percentComplete)")
}
}
}
}
func incrementCurrentPercentageOfAchievement (identifier:String, amount:Double) {
if GKLocalPlayer.localPlayer().authenticated {
var currentPercentFound:Bool = false
if ( gameCenterAchievements.count != 0) {
for (id,achievement) in gameCenterAchievements {
if (id == identifier) {
//progress on the achievement found
currentPercentFound = true
var currentPercent:Double = achievement.percentComplete
currentPercent = currentPercent + amount
reportAchievement(identifier,percentComplete:currentPercent)
break
}
}
}
if (currentPercentFound == false) {
//no progress on the achievement
reportAchievement(identifier,percentComplete:amount)
}
}
}
func reportAchievement (identifier:String, percentComplete:Double) {
let achievement = GKAchievement(identifier: identifier)
achievement.percentComplete = percentComplete
let achievementArray: [GKAchievement] = [achievement]
GKAchievement.reportAchievements(achievementArray, withCompletionHandler: {
error -> Void in
if ( error != nil) {
print(error)
}
else {
print ("reported achievement with % complete of \(percentComplete)")
self.gameCenterAchievements.removeAll()
self.loadAchievementPercentages()
}
})
}
in the view did load I have this
if (conditions for the achievement)
{
incrementCurrentPercentageOfAchievement("achievementID", amount: 100)
}
Tested now and it worked,now i want to do something different,I don't want to increment an achievement percentage but to set the percentage, for example:
Achievement - combo of 10
- Play a game
- Combo of 8 and shows 80% progress on the achievement
- Another game
- Combo of 2
With the code I have now it should unlock the achievement because the percentage is cumulative, so I have to write another function to change the achievement progress percentage and to set it to 20
Now if in a game I met the requirements and unlock the achievement,and in the next game I don't get this condition again, the achievement is still unlocked, right?
I am trying to set up Game Center in a swift sprite kit game. The following is done in my App Delegate
func authenticateLocalPlayer(){
let localPlayer: GKLocalPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = {(viewController : UIViewController!, error : NSError!) -> Void in
//handle authentication
if (viewController != nil){
self.window?.rootViewController?.presentViewController(viewController, animated: true, completion: nil)
}
else{
if (localPlayer.authenticated){
gameCenterEnabled = true
//Get the default leaderboard identifier.
localPlayer.loadDefaultLeaderboardIdentifierWithCompletionHandler({ (leaderboardIdentifierr: String!, error: NSError!) -> Void in
if (error != nil){
NSLog("%#", [error.localizedDescription])
}
else{
leaderboardIdentifier = leaderboardIdentifierr
}
})
}
else{
gameCenterEnabled = false
}
}
}
}
The problem I am having is that the localPlayer.autheniticateHandler always returns a nil viewController even though my local player is not authenticated. Please let me know what I am doing wrong and how to fix this.
I was having the same problem.
Try deleting the app from the device then go to the device's settings under game centre and enable sandbox.
Then run your game again on the device and it should display the game centre login window.
I tried running my app in simulator and the authentication window came up fine. I am not sure why it is not working on my device however.
Exactly the same happens for me. Tried on multiple devices with the same result. Deleting app/changing Game Centre setting won't help.
Your code looks fine though.
It looks like GC for Swift is totally broken..
I'm currently making a 2-player strategy board game in Swift and need to connect two iPads over local WiFi or Bluetooth. No matter what I've tried today, I can't get them to detect each other (I've tried over local WiFi and Bluetooth).
Here is my authorization code which runs in the UIViewController when my app first launches (which always returns "Self local player is authenticated." along with the ID:
private func authenticateLocalPlayer() {
var localPlayer = getLocalPlayer()
// If Apple were doing their job right, this is what the proper code should look like:
// var localPlayer = GKLocalPlayer.localPlayer()
if ( !localPlayer.authenticated ) {
localPlayer.authenticateHandler = { (viewController : UIViewController!, error : NSError!) -> Void in
NSLog("Error: \(error)")
if viewController != nil {
// Authenticated?
self.presentViewController(viewController, animated: true, completion: nil)
NSLog("viewController is not nil")
} else if (localPlayer.authenticated == true) {
NSLog("Self local player is authenticated.")
NSLog("My name is \(localPlayer.playerID)")
} else {
NSLog("Not authenticated")
NSLog("Player is \(localPlayer.playerID)")
}
}
} else {
NSLog("Player is already authenticated!")
}
}
and here is my code to detect nearby devices in a separate UIViewController:
override func viewDidLoad() {
devicesLabel.text = "Waiting for devices..."
searchForDevices()
NSLog("Ran searchForDevices()")
}
private func searchForDevices() {
GKMatchmaker.sharedMatchmaker().startBrowsingForNearbyPlayersWithHandler() {
var status = $1 ? "true" : "false"
self.devicesLabel.text = "Reachability changed for player \($0) with status: \(status)"
}
}
No matter what I do with my two iPads (both are model iPad 3), neither one ever sees the other. Am I calling startBrowsingForNearbyPlayersWithHandler correctly?
Also notice that in the authorization code above, I'm using the Objective-C workaround recommended by this post: Game Center not authenticating using Swift, since the "Swift way" of doing that didn't work for me either.
I also ran Spelltower across both devices over local WiFi, so it looks like the hardware is functioning properly. Any idea what could be going wrong here?
I decided to abandon developing this through Game Center and to use Multipeer Connectivity instead.
You are not registering a class to receive invitation updates. You need to register a class and implement methods conforming to the protocol for GKLocalPlayerListener. See my response in this post (it is in Objective-C, but the same concept applies):
Some startBrowsingForNearbyPlayersWithReachableHandler questions