問題
Unity5(5.2.1)ではUnity付属のSocialPlatformsを使っても、Achievementを送れずエラーになるバグが存在するようです。
参考:http://forum.unity3d.com/threads/problem-with-game-center-achievements.310817/
上記URLを見ると2015年3月ごろから症状が出ているようですが、現時点(2015年10月下旬)では対処された様子がまだありません。
そこで上記URLの解決方法を参考にネイティブプラグインを作って解決することにしました。
対処方法
この記事の最後に掲げる4つのファイル(GCNative.h, GCNative.m, GCNative_Bridge.h, GCNative_Bridge.m)を、Assets/Plugins/iOSフォルダに突っ込んでビルドするだけです。
使い方
使えないのは、Social.ReportProgressだけなので、ネイティブプラグインで置き換えるのは、
Social.ReportProgress(achievementID, 100.0, success => {
Debug.Log(success ? "アチーブメント報告は成功しました" : "アチーブメント報告は失敗しました");
});
のような部分だけになります。
上記のようなコードがあるとしたら、以下のコードで置き換えます。
_ReportAchievement(achievementID, 100.0f);
見ての通りエラーハンドリングは現在のバージョンでは出来ません。
また、上記のコードを使うファイルには、以下のコードも必要になります。詳しくは後掲するコードを参考にしてください。
using System.Runtime.InteropServices; // <- 必須
// ……
[DllImport("__Internal")]
private static extern void _ReportAchievement( string achievementID, float progress );
参考クラス
先に使い方の参考になるクラスを載せます。
実際に使っているコードから抜粋したものなので、各自で修正してお使いください。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.SocialPlatforms;
using System.Runtime.InteropServices;
public class RankingUtility {
[DllImport("__Internal")]
private static extern void _ReportAchievement( string achievementID, float progress );
public delegate void LoadAchievementsHandler(int handlerArgument);
//このIDは任意の登録したものを使う
const string leaderboardID = "score";
static bool successAuth = false;
//ユーザー認証
public static void Auth() {
// 認証のため ProcessAuthenticationをコールバックとして登録
Social.localUser.Authenticate (ProcessAuthentication);
}
// 認証が完了した時に呼び出される
// 認証が成功した場合、サーバーからのデータがSocial.localUserにセットされている
private static void ProcessAuthentication (bool success) {
successAuth = success;
if (success) {
Debug.Log ("Authenticated, checking achievements");
} else {
Debug.Log ("Failed to authenticate");
}
}
// リーダーボードを表示する
public static void ShowLeaderboardUI() {
if( ! successAuth ) return;
Social.ShowLeaderboardUI();
}
// リーダーボードにスコアを送信する
public static void ReportScoreNormal (long score) {
if( ! successAuth ) return;
Debug.Log ("スコア " + score + " を次の Leaderboard に報告します。" + leaderboardID);
Social.ReportScore (score, leaderboardID, success => {
Debug.Log(success ? "スコア報告は成功しました" : "スコア報告は失敗しました");
});
}
private static IAchievement[] currentAchivements = null;
private static Dictionary<string, IAchievement> dic = null;
// 現在のアチーブメントの状況を取得する。
public static void LoadAchievements(LoadAchievementsHandler handler, int handlerArg = 0){
Social.LoadAchievements(achievements => {
dic = new Dictionary<string, IAchievement>();
foreach(var achievement in achievements){
var percent = achievement.percentCompleted;
var completed = achievement.completed;
var id = achievement.id;
dic[id] = achievement;
Debug.Log ("" + id + ": percent = " + percent + ", completed = " + completed);
}
currentAchivements = achievements;
if(handler != null){
handler(handlerArg);
}
});
}
// Achivementの進捗状況を送信する(メソッド名は気にしない)。
//呼び出し例:RankingUtility.ReportAchievementScore("SomeAchievement");
//
public static void ReportAchievementScore (string achievementID) {
Debug.Log ("アチーブメント" + achievementID + " を送信します。");
/* ここを置き換えた。
Social.ReportProgress(achievementID, 100.0, success => {
Debug.Log(success ? "アチーブメント報告は成功しました" : "アチーブメント報告は失敗しました");
});
*/
_ReportAchievement(achievementID, 100.0f);
}
}
ネイティブプラグイン
以下の4つのファイルをAssets/Plugins/iOSフォルダに入れます。
#ifndef GCNative_Bridge_h
#define GCNative_Bridge_h
void _ReportAchievement( const char* achievementID, float progress );
#endif /* GCNative_Bridge_h */
#import <Foundation/Foundation.h>
#include "GCNative_Bridge.h"
#include "GCNative.h"
void _ReportAchievement( const char* achievementID, float progress ){
NSString* str = [[NSString alloc] initWithUTF8String:achievementID];
GCNative* native = [[GCNative alloc] init];
[native submitAchievement:str percentComplete:progress];
}
#ifndef GCNative_h
#define GCNative_h
#import <Foundation/Foundation.h>
#import <GameKit/GameKit.h>
@protocol GameCenterManagerDelegate <NSObject>
@optional
- (void) processGameCenterAuth: (NSError*) error;
- (void) scoreReported: (NSError*) error;
- (void) reloadScoresComplete: (GKLeaderboard*) leaderBoard error: (NSError*) error;
- (void) achievementSubmitted: (GKAchievement*) ach error:(NSError*) error;
- (void) achievementResetResult: (NSError*) error;
- (void) mappedPlayerIDToPlayer: (GKPlayer*) player error: (NSError*) error;
@end
@interface GCNative : NSObject
-(void)submitAchievement: (NSString*)identifier percentComplete: (double)percentComplete;
@property (retain) NSMutableDictionary* earnedAchievementCache;
@property (nonatomic, assign) id <GameCenterManagerDelegate> delegate;
@end
#endif /* GCNative_h */
#import "GCNative.h"
@implementation GCNative
@synthesize earnedAchievementCache = _earnedAchievementCache;
@synthesize delegate = _delegate;
-(id)init {
self = [super init];
if(self!= NULL) {
_earnedAchievementCache= NULL;
}
return self;
}
-(void)dealloc {
self.earnedAchievementCache= NULL;
//[super dealloc];
}
- (void) callDelegate: (SEL) selector withArg: (id) arg error: (NSError*) err {
assert([NSThread isMainThread]);
if([_delegate respondsToSelector: selector]) {
if(arg != NULL) {
[_delegate performSelector: selector withObject: arg withObject: err];
} else {
[_delegate performSelector: selector withObject: err];
}
} else {
NSLog(@"Missed Method");
}
}
- (void) callDelegateOnMainThread: (SEL) selector withArg: (id) arg error: (NSError*) err {
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self callDelegate: selector withArg: arg error: err];
});
}
-(void)submitAchievement: (NSString*)identifier percentComplete: (double)percentComplete {
if(self.earnedAchievementCache == NULL) {
[GKAchievement loadAchievementsWithCompletionHandler: ^(NSArray *scores, NSError *error) {
if(error == NULL) {
NSMutableDictionary* tempCache= [NSMutableDictionary dictionaryWithCapacity: [scores count]];
for (GKAchievement* score in scores) {
[tempCache setObject: score forKey: score.identifier];
}
self.earnedAchievementCache= tempCache;
[self submitAchievement: identifier percentComplete: percentComplete];
} else {
//Something broke loading the achievement list. Error out, and we'll try again the next time achievements submit.
//[self callDelegateOnMainThread: @selector(achievementSubmitted:error:) withArg: NULL error: error];
}
}];
} else {
//Search the list for the ID we're using...
GKAchievement* achievement= [self.earnedAchievementCache objectForKey: identifier];
if(achievement != NULL) {
if((achievement.percentComplete >= 100.0) || (achievement.percentComplete >= percentComplete)) {
//Achievement has already been earned so we're done.
achievement= NULL;
}
achievement.percentComplete= percentComplete;
} else {
achievement= [[GKAchievement alloc] initWithIdentifier: identifier];
achievement.percentComplete= percentComplete;
//Add achievement to achievement cache...
[self.earnedAchievementCache setObject: achievement forKey: achievement.identifier];
}
if(achievement != NULL) {
//Submit the Achievement...
[achievement reportAchievementWithCompletionHandler: ^(NSError *error) {
//[self callDelegateOnMainThread: @selector(achievementSubmitted:error:) withArg: achievement error: error];
}];
}
}
}
@end
参考URL
http://forum.unity3d.com/threads/problem-with-game-center-achievements.310817/
UnityでiOSのGameCenterを利用するためのサンプルコード
AppleのサンプルプロジェクトGKTapperのソース