GameKit II: Creating the GameCenterManager class


Hi all!

On my last post I stated that I was planning not to use a ViewController object on top of my GameCenter stuff. After reading carefully the developer guide and the example code at Apple Developer resources Apple – GKTapper, I decided to make a little twist on my base idea and change the screen I use at the matches end:

 

It was a regular GameScene object with the typical game loop as Mike explains on his web, but I changed it into a ViewController based object from wich I’ll post NSNotifications in order to open local or global scores. If GameCenter is not supported on the device or the player did cancel the GC authentication, only Local Highscores should be available.

I also found out some odd details on GC leaderboards, like you can’t have more than one score entry, all the different leaderboard categories must share the same score Format Type and so on, so I’ll keep my old Highscores window to keep track of the local improvments on the score. To bypass the first obstacle regarding the limitation of one score per player at the leaderboard, we must make sure that the current or last score is better than the previous submitted to GC one.

If it’s better we will submit it, if it’s not we add an entry in the local Highscore array that we are storing locally using NSDefault. That’s the purpose of the variables present in the example code related to player’s best score.

But I’d better start from the beggining.

What we are going to do is to create a GameCenterManager which .h file looks like this:

GameCenterManager.h

@protocol GameCenterManagerDelegate <NSObject>
@optional
– (void) processGameCenterAuth: (NSError*) error;
– (void) scoreReported: (NSError*) error;
– (void) reloadScoresComplete: (GKLeaderboard*) leaderBoard error: (NSError*) error;
– (void) mappedPlayerIDToPlayer: (GKPlayer*) player error: (NSError*) error;
@end
@interface GameCenterManager : NSObject
{
id <GameCenterManagerDelegate, NSObject> delegate;
}
//This property must be attomic to ensure that the cache is always in a viable state…
@property (retain) NSMutableDictionary* earnedAchievementCache;
@property (nonatomic, assign)  id <GameCenterManagerDelegate> delegate;
+ (BOOL) isGameCenterAvailable;
– (void) authenticateLocalUser;
– (void) reportScore: (int64_t) score forCategory: (NSString*) category;
– (void) reloadHighScoresForCategory: (NSString*) category;
– (void) mapPlayerIDtoPlayer: (NSString*) playerID;
@end

The first part declares the protocol that states the methods that an object must implement to be able to delegate some of the GC functionality on it. If you’re not familiar with delegating, in this example you will clearly see how it works:

  • The delegate object sends a score in order to be reported.
  • The GameKitManager object submits the score to GameCenter and delegates the actions to follow with the authentication result to the class delegate.

In my case the new GameEndViewController class will be the delegate of the GameCenterManager class and will be responsible of enabling gamecenter features if we did log in successfully.

The method the manager implements are:

– (void) authenticateLocalUser;
– (void) reportScore: (int64_t) score forCategory: (NSString*) category;
– (void) reloadHighScoresForCategory: (NSString*) category;
– (void) mapPlayerIDtoPlayer: (NSString*) playerID;

I’m not sure what is “mapPlayerIDtoPlayer” but it’s  on the example code and I won’t delete it until I’m done and sure it’s useless.

“authenticateLocalUser” is self explanatory.

“reportScore” and “reloadHighScoresForCategory” submit and request GC for the leaderboards information. I won’t need to request the leaderboard scores but I find it useful to have it here.

This is the delegate protocl declaration:

@protocol GameCenterManagerDelegate <NSObject>
@optional
– (void) processGameCenterAuth: (NSError*) error;
– (void) scoreReported: (NSError*) error;
– (void) reloadScoresComplete: (GKLeaderboard*) leaderBoard error: (NSError*) error;
– (void) mappedPlayerIDToPlayer: (GKPlayer*) player error: (NSError*) error;
@end

As you can see, all the methods respond to the actions our app may perform with the return of the GC requests that the manager performs.

“processGameCenterAuth” will notify the user if there was any trouble authenticating their GC account.

“scoreReported” will prompt the user if there was any trouble reporting a score.

GameCenterManager.m

This class wil implement the methods we declared to report and retrieve scores from GC, but the most important and tricky part is the way it comunicates with the delegate. GameCenter responses are sended asynchronously because we can’t make our apps wait for a network resource. Out code execution will continue and eventually the server response will arrive and our delegate must handle that.

Also, if you send messages to a viewController like

[aViewController processSomething]

It will dealloc and init the object if it’s done from a thread different than the main thread and GameCenter does not guarantee that callback blocks will be execute on the main thread. There are a number of ways you can bypass this. I’ve become familiar with NSNotifications and I feel comfortable with them. With this approach you are posting notifications and only the objects that are “observing” that notification in particular will react to it with a selector. This won’t alloc/dealloc anything and although I’m not sure it’s the best solution, I’ll go for it if the source code of GKTapper shows any issue.

The way it works on the example code is something like:

  • Make sure we are on the mainThread
  • Make sure that the delegate responds to a particular selector
  • Perform selector

And this is the code responsible of calling back the delegate after performing a GC request.

– (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];
});
}

All the methods on GameCenterManager.m will use the callDelegateOnMainThread to manage GC asynchronous responses like this:

– (void) authenticateLocalUser
{
if([GKLocalPlayer localPlayer].authenticated == NO)
{
[[GKLocalPlayer localPlayer] authenticateWithCompletionHandler:^(NSError *error)
{
[self callDelegateOnMainThread: @selector(processGameCenterAuth:) withArg: NULL error: error];
}];
}
}

This line shows how we call our delegate passing the “processGameCenterAuth” selector.

[self callDelegateOnMainThread: @selector(processGameCenterAuth:) withArg: NULL error: error];

Our ViewController must implement that method to handle the GC response.

That’s all about the GameCenterManager, I’ll be testing it this weekend and I’ll report with the experience and the delegate selector methods implementations.

“Thanks for watching!”

Anuncios

Acerca de lapsusmental

Computer engineer working as a Banking Applications Developer in a multinacional firm. Actually, this blog is intended to cover my latest, and more exciting, hobby I'm spending my spare time on: iPhone Game Development. Everything I managed to learn on my own or visiting the many sites where more skilled and experienced people share they knowloedge has room in this site.
Esta entrada fue publicada en Tutorials y etiquetada , , , , , , , , , , , , . Guarda el enlace permanente.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s