Home Tutorials Download Beta Store Forum Documentation KnowledgeBase Wiki Blog
Main Page About Help FAQ Special pages Log in

IOS GameCenter

From ShiVa Wiki

Contents

About GameCenter

Game Center is Apple's scores and achivements handling system, which is integrated into almost all games on iphone/ipad. Sure there are other features that GC has, but in most cases we need login, submitting scores, achievements, viewing leaderboards pages etc, so let's do it.

First of all I will describe the way I've integrated it. There are few ways like plugins, compiling lua to c++ etc, but I did with lua+ObjC+an engine-wrapper. So you can call GameCenter's stuff directly from lua and with some changes in the xCode project files you will have it working on your iOS device.

Integration: Login

onInit

Im doing everything in the Main AI of my game, but surely you can do it anywhere in your lua scripts. So we have the onInit() function of our game, where everything starts.

user.postEvent ( application.getCurrentUser ( ),0,"MainAI","onInitGameCenter" )

and created an empty handler onInitGameCenter (NOT A FUNCTION, only handlers work).

S3DEngine_Wrapper.cpp

Now we need to catch this call in ObjC/Cpp code. Once we created a xCode proj, we open S3DEngine_Wrapper.cpp and search for "C" bool S3DEngine_iPhone_LoadPack. Add:

S3DClient_InstallCurrentUserEventHook   ( "MainAI", "onInitGameCenter",  GameCenterSetInitCallback, NULL ) ;

Don't forget to declare GameCenterSetInitCallback first:

void GameCenterSetInitCallback ( unsigned char _iArgumentCount, const void *_pArguments, void *_pUserData )
{
   bGameCenterWantInit = true;
}

We need to make a link between the wrapper and the ObjC code. That's why we will use the "bGameCenterWantInit" boolean variable, later we will handle this boolean to show/close GameCenter stuff etc. sure we need declare this variable, so we are going to the beginning of the file and add

static bool bGameCenterWantInit = false;

Also we need 2 functions to read and handle this boolean from ObjC code, so at the end of this file (before AdMob integration) we need to add:

extern "C" bool S3DEngine_GameCenter_WantInit ( )
{
    return bGameCenterWantInit ;
}
 
extern "C" void S3DEngine_GameCenter_SetWantInitFalse ( )
{
    bGameCenterWantInit = false;
}

S3DEngine_Wrapper.h

In the Header file, you need to add

extern bool         S3DEngine_GameCenter_WantInit               ( );
extern void         S3DEngine_GameCenter_SetWantInitFalse         ( );

now we need to call something from it from ObjC code because only ObjC can handle delegates, so we will be able to have controls on UIViews etc.

S3DEngine_EAGLView.h

Add to the top of the file:

#import <GameKit/GameKit.h>

S3DEngine_EAGLView.m

Search for "(void)drawView". This function is called every frame. As we have booleans, we can do something just "once", like call GameCenter login. We will also implement callbacks to scripts, so we will be able to handle everything within lua, like our GameCenter init and login status. so in this function, just after ad-mob checks, we're adding

         if ( S3DEngine_GameCenter_WantInit ( ) )
         {
            S3DEngine_GameCenter_SetWantInitFalse();
            [self authenticateLocalPlayer];            
         }

As you can see, we call GameCenter and change the boolean to false to prevent further calls. Now probably you're curious what "authenticateLocalPlayer" is? : ) That is the start of our ObjC code, so lets program that as well... in the same S3DEngine_EAGLView.m file, locate "(void)showVirtualKeyboard" and add before it,

//-----------------
// GAME CENTER
 
 
BOOL isGameCenterAvailable()
{
    // Check for presence of GKLocalPlayer API.   
    Class gcClass = (NSClassFromString(@"GKLocalPlayer"));   
 
    // The device must be running running iOS 4.1 or later.   
    NSString *reqSysVer = @"4.1";   
    NSString *currSysVer = [[UIDevice currentDevice] systemVersion];   
    BOOL osVersionSupported = ([currSysVer compare:reqSysVer options:NSNumericSearch] != NSOrderedAscending);   
 
    return (gcClass && osVersionSupported);
}
 
- (void)authenticateLocalPlayer {
 
   if(!isGameCenterAvailable()){
      return;
   }
    [[GKLocalPlayer localPlayer] authenticateWithCompletionHandler:^(NSError *error) {      
      if (error == nil){
         [self registerForAuthenticationNotification];
         S3DClient_SendEventToCurrentUser("MainAI","onGameCenterLogged",NULL);
      }else{
         S3DClient_SendEventToCurrentUser("MainAI","onGameCenterOut",NULL);
      }
   }];
}
 
- (void)registerForAuthenticationNotification
{
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    [nc addObserver: self selector:@selector(authenticationChanged) name:GKPlayerAuthenticationDidChangeNotificationName object:nil];
}
 
- (void)authenticationChanged
{
   if(!isGameCenterAvailable()){
      return;
   }
 
    if ([GKLocalPlayer localPlayer].isAuthenticated){      
      S3DClient_SendEventToCurrentUser("MainAI","onGameCenterLogged",NULL);
   }else{
      S3DClient_SendEventToCurrentUser("MainAI","onGameCenterOut",NULL);
   }
}

As you can see, we have 2 new callbacks "onGameCenterLogged" and "onGameCenterOut". You must add these to your lua scripts - create new 2 handlers that access your global environment variables - to know if the user has logged into GameCenter or quit. Newer iOS revisions have multitasking, so sometimes it can happen that your app can go into pause and the player logs out from GameCenter and logs in again with another account, so keep that in mind that GameCenter logins/outs can happen at any time, not only at the start of your app. In this part of code we are also checking if GameCenter is available.

S3DEngine_EAGLView.h

add

- (void)authenticateLocalPlayer;
- (void)registerForAuthenticationNotification;
- (void)authenticationChanged;

before "@end".

ok, now you may try to compile the sources and see what happens. At this point we have only integrated GameCenter login.

Integration: Leaderboard, Achievements, High Score

S3DEngine_EAGLView.h

to get more features, add these:

- (void)leaderboardViewControllerDidFinish:(GKLeaderboardViewController *)viewController;
- (void)showLeaderboard;
 
- (void)showAchievements;
- (void)achievementViewControllerDidFinish:(GKAchievementViewController *)viewController;
 
- (void)reportScore;
- (void)reportAchiv;

S3DEngine_EAGLView.m

search for "(void)drawView " and add

         if ( S3DEngine_GameCenter_WantVisible ( ) )
         {
            S3DEngine_GameCenter_SetWantVisibleFalse();
            [self showLeaderboard];            
         }
 
         if (S3DEngine_GameCenter_WantAchivs())
         {
            S3DEngine_GameCenter_SetWantAchivsFalse();
            [self showAchievements];
         }
 
         if (S3DEngine_GameCenter_WantSubmitScores())
         {
            S3DEngine_GameCenter_SetWantSubmitScoresFalse();
            [self reportScore];
         }
 
         if (S3DEngine_GameCenter_WantSubmitAchiv())
         {
            S3DEngine_GameCenter_SetWantSubmitAchivFalse();
            [self reportAchiv];
         }         
 

also, locate "(void)showVirtualKeyboard" and add the following (please replace achievement names with yours (like AchivName1) and leaderboard name LeaderboardName1):

- (void)showLeaderboard
{
 
   UIWindow* window = [UIApplication sharedApplication].keyWindow;
 
    GKLeaderboardViewController *leaderboardController = [[GKLeaderboardViewController alloc] init];   
    if (leaderboardController != nil) {
      leaderboardController.leaderboardDelegate = self;
 
      UIViewController *glView2 = [[UIViewController alloc] init];
      [window addSubview: glView2.view];
        [glView2 presentModalViewController: leaderboardController animated: YES];
    }
 
}
 
- (void)leaderboardViewControllerDidFinish:(GKLeaderboardViewController *)viewController
{
    [viewController dismissModalViewControllerAnimated:YES];
   [viewController.view.superview removeFromSuperview];
   [viewController release];
}
 
- (void)showAchievements
{   
 
   UIWindow* window = [UIApplication sharedApplication].keyWindow;
 
    GKAchievementViewController *achievements = [[GKAchievementViewController alloc] init];   
    if (achievements != nil){
        achievements.achievementDelegate = self;
      UIViewController *glView2 = [[UIViewController alloc] init];
      [window addSubview: glView2.view];
        [glView2 presentModalViewController: achievements animated: YES];      
    }   
}
 
- (void)achievementViewControllerDidFinish:(GKAchievementViewController *)viewController
{
    [viewController dismissModalViewControllerAnimated:YES];
   [viewController.view.superview removeFromSuperview];
   [viewController release];
}
 
- (void)reportScore
{
 
    GKScore *scoreReporter = [[[GKScore alloc] initWithCategory:@"LeaderboardName1"] autorelease];
   if(scoreReporter){
      scoreReporter.value = S3DEngine_GameCenter_GetScoresToSubmit();   
 
      [scoreReporter reportScoreWithCompletionHandler:^(NSError *error) {   
         if (error != nil){
            // handle the reporting error
 
         }
      }];   
   }
}
 
- (void) reportAchiv
{
 
   int m = S3DEngine_GameCenter_GetAchivToSubmit();
 
   NSString *achievementID = [[NSString alloc] autorelease];
   switch (m){
      case 1:
         achievementID = @"AchivName1";
         break;
      case 2:
         achievementID = @"AchivName2";
         break;
 
      }
 
      GKAchievement *achievement = [[[GKAchievement alloc] initWithIdentifier: achievementID] autorelease];   
      if (achievement){      
         achievement.percentComplete = 100.0f;      
         [achievement reportAchievementWithCompletionHandler:^(NSError *error){
         if (error != nil){
 
         }       
      }];
    }   
 
 
}

S3DEngine_Wrapper.h

extern bool         S3DEngine_GameCenter_WantVisible            ( );
extern void         S3DEngine_GameCenter_SetWantVisibleFalse      ( );
 
extern bool         S3DEngine_GameCenter_WantAchivs               ( );
extern void         S3DEngine_GameCenter_SetWantAchivsFalse         ( );
 
extern bool         S3DEngine_GameCenter_WantSubmitScores         ( );
extern void         S3DEngine_GameCenter_SetWantSubmitScoresFalse   ( );
extern int         S3DEngine_GameCenter_GetScoresToSubmit         ( );
 
extern bool         S3DEngine_GameCenter_WantSubmitAchiv         ( );
extern void         S3DEngine_GameCenter_SetWantSubmitAchivFalse   ( );
extern int         S3DEngine_GameCenter_GetAchivToSubmit         ( );
 

S3DEngine_Wrapper.cpp

add to the top of the file,

static bool bGameCenterWantVisible = false;
static bool bGameCenterWantAchivs = false;
 
static bool   bGameCenterWantSubmitScores=false;
static int nScoresToSubmit = 0;
 
static bool   bGameCenterWantSubmitAchiv=false;
static int nAchivToSubmit = 0;

add this before "extern "C" bool S3DEngine_iPhone_LoadPack":

void GameCenterSetVisibleCallback ( unsigned char _iArgumentCount, const void *_pArguments, void *_pUserData )
{   
   bGameCenterWantVisible = true;
}
 
void GameCenterSetAchivsCallback ( unsigned char _iArgumentCount, const void *_pArguments, void *_pUserData )
{
   bGameCenterWantAchivs = true;
}
 
void GameCenterSubmitScoresCallback ( unsigned char _iArgumentCount, const void *_pArguments, void *_pUserData )
{
   bGameCenterWantSubmitScores = true;
   const S3DX::AIVariable *pVariables = (const S3DX::AIVariable *)_pArguments ;
   nScoresToSubmit   = pVariables[0].GetNumberValue ( );
}
 
void GameCenterSubmitAchivCallback ( unsigned char _iArgumentCount, const void *_pArguments, void *_pUserData )
{
   bGameCenterWantSubmitAchiv = true;
   const S3DX::AIVariable *pVariables = (const S3DX::AIVariable *)_pArguments ;
   nAchivToSubmit   = pVariables[0].GetNumberValue ( );
}

and this into "extern "C" bool S3DEngine_iPhone_LoadPack":

   S3DClient_InstallCurrentUserEventHook   ( "MainAI", "onShowLeaderboard", GameCenterSetVisibleCallback, NULL ) ;
   S3DClient_InstallCurrentUserEventHook   ( "MainAI", "onShowAchievements", GameCenterSetAchivsCallback, NULL ) ;
 
   S3DClient_InstallCurrentUserEventHook   ( "MainAI", "onSubmitScores",  GameCenterSubmitScoresCallback, NULL ) ;
   S3DClient_InstallCurrentUserEventHook   ( "MainAI", "onSubmitAchiv",  GameCenterSubmitAchivCallback, NULL ) ;
 
 

and at the end of the file:

extern "C" bool S3DEngine_GameCenter_WantVisible ( )
{
    return bGameCenterWantVisible ;
}
 
extern "C" void S3DEngine_GameCenter_SetWantVisibleFalse ( )
{
    bGameCenterWantVisible = false;
}
 
extern "C" bool S3DEngine_GameCenter_WantAchivs ( )
{
    return bGameCenterWantAchivs ;
}
 
extern "C" void S3DEngine_GameCenter_SetWantAchivsFalse ( )
{
    bGameCenterWantAchivs = false;
}
extern "C" bool S3DEngine_GameCenter_WantSubmitScores ( )
{
    return bGameCenterWantSubmitScores ;
}
extern "C" void S3DEngine_GameCenter_SetWantSubmitScoresFalse ( )
{
    bGameCenterWantSubmitScores = false;
}
extern "C" int S3DEngine_GameCenter_GetScoresToSubmit()
{
   return nScoresToSubmit;
}
extern "C" bool S3DEngine_GameCenter_WantSubmitAchiv ( )
{
    return bGameCenterWantSubmitAchiv ;
}
extern "C" void S3DEngine_GameCenter_SetWantSubmitAchivFalse ( )
{
    bGameCenterWantSubmitAchiv = false;
}
extern "C" int S3DEngine_GameCenter_GetAchivToSubmit()
{
   return nAchivToSubmit;
}

as you can see, you must also add the handlers onShowLeaderboard, onShowAchievements, onSubmitScores and onSubmitAchiv. Use simple integer values as parameters in lua to send. For achievements use 1,2...n to submit the index of the achievement you want, then "switch case" to send the actual achievement name.

ADDITIONAL [SCORES RETRIEVING FROM LEADERBOARD]

Here I'll describe how I've coded the logic for getting N highscores from GameCenter's leaderboard and delivering them to lua.

We will create a few handlers in lua which will send "requests" to c++/objC and will get callbacks.

MainAI

Create 2 variables in your main ai model: let's call them "tableNames" and "tableScores", they should have "Table" as type and be empty for now. We will use these tables for saving received highscores with the players' real names.

 
    table.empty ( this.tableScores ( ) )
    table.empty ( this.tableNames ( ) )
    user.postEvent ( application.getCurrentUser ( ),0,"MainAI","onFillMarkersTable" )

As you can see, we have a new handler, "onFillMarkersTable" - you should create an empty handler with this name in your ai model.

S3DEngine_Wrapper.cpp

add on top to the other variables:

static bool bGameCenterWantMarkers = false;

at the end of this file (before ad-mob integrations in most cases) we're adding:

extern "C" bool S3DEngine_GameCenter_WantMarkers ( )
{
    return bGameCenterWantMarkers ;
}
extern "C" void S3DEngine_GameCenter_SetWantMarkersFalse ( )
{
    bGameCenterWantMarkers = false;
}

Locate "C" bool S3DEngine_iPhone_LoadPack" and add a hook:

   S3DClient_InstallCurrentUserEventHook   ( "MainAI", "onFillMarkersTable",  GameCenterMarkersCallback, NULL ) ;

Before the function, add:

void GameCenterMarkersCallback ( unsigned char _iArgumentCount, const void *_pArguments, void *_pUserData )
{
   bGameCenterWantMarkers = true;
}

S3DEngine_Wrapper.h

extern bool         S3DEngine_GameCenter_WantMarkers               ( );
extern void         S3DEngine_GameCenter_SetWantMarkersFalse         ( );

S3DEngine_EAGLView.m

search for "- (void)drawView " and add

         if (S3DEngine_GameCenter_WantMarkers())
         {
            S3DEngine_GameCenter_SetWantMarkersFalse();
            [self fillMarkersTable];
         }

As you understand, we need to create the "fillMarkersTable" function, which will do all work.

- (void) fillMarkersTable
{
 
 
   GKLeaderboard* leaderBoard = [[[GKLeaderboard alloc] init] autorelease];
   leaderBoard.category = @"LeaderboardName1";
   leaderBoard.timeScope = GKLeaderboardTimeScopeAllTime;
   leaderBoard.range = NSMakeRange(1,50);
 
   [leaderBoard loadScoresWithCompletionHandler: ^(NSArray *scoress, NSError *error)
    {
       int n =0;
       for ( n = 0; n < [scoress count]; n++ )
       {
          GKScore* a = [scoress objectAtIndex:n];
 
          [GKPlayer loadPlayersForIdentifiers: [NSArray arrayWithObject: [a playerID]] withCompletionHandler:^(NSArray *playerArray, NSError *error)
           {
              GKPlayer* player= NULL;
              for (GKPlayer* tempPlayer in playerArray)
              {
                 if([tempPlayer.playerID isEqualToString: [a playerID]])
                 {
                    player= tempPlayer;
                    NSString* rez = player.alias;
                    const char*cString = [[a formattedValue] UTF8String];
                    const char*cString2 = [rez UTF8String];
 
                    const void *params = [OurConvert getOurConvert:cString2 andY:cString];
                    S3DClient_SendEventToCurrentUser("MainAI","onGetNewMarkerData",2,params);                    
 
                    break;
                 }
              }
           }];          
       }
    }];   
 
 
}

"LeaderboardName1" - you should change that to your leaderboard name "leaderBoard.range = NSMakeRange(1,50);" - you can change it to e.g. 1,100 so you'll get the first 100 entries of the highscores leaderboard. "const void *params = [OurConvert getOurConvert:cString2 andY:cString];"

We have to convert values to AIVariables, so we called the (yet) non-existing getOurConvert function in the OurConvert interface.

S3DEngine_EAGLView.h

declare

- (void)fillMarkersTable;

Create 2 new files

create "OurConvert.h" and "OurConvert.mm" and add them to xCode's tree and import them.

S3DEngine_EAGLView.m

add

#import "OurConvert.h"

OurConvert.h

#import <Foundation/Foundation.h>
@interface OurConvert: NSObject{ }
+(const void*)getOurConvert:(const char*)s1 andY:(const char*)s2;
@end

OurConvert.mm

#import "OurConvert.h"
#import "S3DXPlugin.h"
@implementation OurConvert
+(const void*)getOurConvert:(const char*)s1 andY:(const char*)s2{
   static S3DX::AIVariable args[2];
   args[0].SetStringValue(s1);
   args[1].SetStringValue(s2);
   return args;
}
@end

MainAI.onGetNewMarkerData

--------------------------------------------------------------------------------
function MainAI.onGetNewMarkerData (  sName, sScore  )
--------------------------------------------------------------------------------
 
    local s = ""
    local n = string.getLength ( sScore )
    local sTheScore = ""
 
    for i=0,n-1
    do
        s = string.getSubString ( sScore, i, 1 )
        if ((s == "0") or (s == "1") or (s == "2") or (s == "3") or (s=="4") or (s=="5") or (s=="6") or (s=="7") or (s=="8") or (s=="9"))
        then
            sTheScore = sTheScore .. s
        else
            break
        end
    end
 
    table.add ( this.tableNames ( ), sName )
    table.add ( this.tableScores( ), sTheScore )
 
--------------------------------------------------------------------------------
end
--------------------------------------------------------------------------------

In this way, we can will fill our table-type variables.

call

local tName = table.getFirst ( this.tableNames() )
local tScore = table.getFirst ( this.tableScores () )

to get data from those values and create score markers like in Doodle Jump game or something ; ) I've also done simple checks for digits-only filtering, because in most cases, scores from GameCenter look like "120m" or "1500 points", so we need to filter chars and have real numbers ; ) enjoy ; )

Credits

ShiVa user vklymenko.

Click Here to read the original Thread.

Special thanks to DaveYoung, Chris, Dell and all who are spending time on the irc chat.



Retrieved from "http://www.stonetrip.com/developer/wiki/index.php?title=IOS_GameCenter"

This page has been accessed 3,082 times. This page was last modified on 20 February 2011, at 00:21.