Tutorial: Game Center Leaderboards

Hallo Zusammen,

heute geht es um Game Center Leaderboards. Wahrscheinlich hat sich jeder der schon mal ein Spiel gespielt hat bewusst oder unbewusst in ein Leaderboard eingetragen. Leaderboards sind Ranglisten eines Spiels – wer ist schnellste, war sammelt die meisten Punkte?

In diesem Tutorial zeige ich euch, wie ihr Leaderboards in iTunes Connect erstellt und aus eurer App heraus Punkte berichtet.

Wir starten mit einem neuen Projekt und wählen Single View Application.

In dem Projekt erstellen wir zuerst eine neue Subklasse von NSObject (New —> File —> Objective C Class). Die Klasse nennen wir LeaderboardHelper.

Die Idee ist hier, alle Aufgaben die mit dem Leaderboard zutun haben von dieser Klasse verwalten zu lassen. Das hat den großen Vorteil, dass ihr die Klasse in anderen Projekten weiterverwenden könnt. Kommt die nächste App, welche auch Leaderboards anbieten soll – toll. Kopiert die hier erstellte Klasse einfach ins Projekt und fast alle Aufgaben sind schon erledigt. Genug der Theorie. Beginnen wir in LeaderboardHelper.h Wir sollten zunächst überlegen, welche Methoden wir brauchen werden.

+ (LeaderboardHelper *)sharedInstance;
- (void)authenticateLocalUser;
- (void) reportScore: (int64_t) score forLeaderboardID: (NSString*) identifier;

Wir wollen auf das gleiche Objekt eines LeaderboardHelpers zugreifen und implementieren daher eine sharedInstance Klassenmethode. Wie das geht sehen wir später. Des Weiteren benötigen wir eine Möglichkeit den aktuellen Nutzer am GameCenter anzumelden und zu guter Letzt brauchen wir eine Methode um die Punkte zu berichten.

Über den Methoden ergänzen wir drei Property. Wir möchten im ersten Property gerne speichern wem LeaderboardHelper nach dem Berichten der Punkte eine Antwort geben soll. In userAuthenticated merken wir uns, ob der lokale Nutzer angemeldet ist. Außerdem halten wir eine Referenz zu einem authenticationViewController. Diesen müssen wir anzeigen können, falls der Bedarf besteht, dass der Nutzer sich anmelden

@property (nonatomic, weak)     id <LeaderboardHelperDelegate> leaderboardDelegate;
@property (assign, nonatomic)   BOOL  userAuthenticated;
@property (nonatomic, readonly) UIViewController *authenticationViewController;

Nun müssen wir noch das Delegate Protokoll schreiben. Hier definieren wir welche Methoden eine Klasse implementiert haben muss, damit sie den LeaderboardHelper Nutzer kann. Für unsere Zwecke soll es genügen dem aufrufenden Objekt die Information zurückzugeben, dass die Punkte erfolgreich berichtet wurden. Natürlich sind hier auch noch komplexere Konstrukte möglich.

Nun müssen wir noch das Delegate Protokoll schreiben. Hier definieren wir welche Methoden eine Klasse implementiert haben muss, damit sie den LeaderboardHelper Nutzer kann. Für unsere Zwecke soll es genügen dem aufrufenden Objekt die Information zurückzugeben, dass die Punkte erfolgreich berichtet wurden. Natürlich sind hier auch noch komplexere Konstrukte möglich.

@class LeaderboardHelper;
@protocol LeaderboardHelperDelegate <NSObject>
- (void)leaderboardHelperReportedScore:(LeaderboardHelper *)leaderboardHelper forIdentifier:(NSString*)identifier;
@end

Springen wir in Leaderboard.m
Als aller erstes importieren wir das GameKit Framework.
#import <GameKit/GameKit.h>
Zunächst implementieren wir die sharedInstace Methode. Immer wenn sharedInstance aufgerufen wird, erfolgt eine Prüfung, ob das Objekt bereits vorhanden ist. Sollte dem nicht so sein, wird ein Objekt LeaderboardHelper erstellt. In beiden Fälle wird das Objekt dann über return sharedHelper zurückgegeben.

static LeaderboardHelper *sharedHelper = nil;
+ (LeaderboardHelper *) sharedInstance {
    if (!sharedHelper) {
        sharedHelper = [[LeaderboardHelper alloc] init];
  }
    return sharedHelper;
}

In unserer init Funktion machen wir nicht wahnsinnig. Wir registrieren uns lediglich für eine lokale Notification. Auf diese Weise wird unsere Methode authenticationChanged jedes mal aufgerufen, wenn der  Nutzer sich an- oder abmeldet. 

- (id)init {
    if ((self = [super init])) {        
    }
    return self;
}

Die Methode authenticateLocalUser soll den lokalen Nutzer am GameCenter anmelden. Gleichzeitig installieren wir einen authenticateHandler. Alles nach dem ^ ist eine separate Methode, welche immer dann aufgerufen wird, wenn der Nutzer sich angemeldet hat oder wenn ein Anmeldebildschirm erscheinen soll.

- (void) authenticateLocalUser
{
    NSLog(@"Authenticating local user...");
    GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
    localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error){
		if(viewController != nil) {
            NSLog(@"viewController != nil");
			_authenticationViewController = viewController;
        } else if([GKLocalPlayer localPlayer].isAuthenticated) {
            NSLog(@"isAuthenticated");
            self.userAuthenticated = YES;
        } else {
            NSLog(@"not authenticated");
            self.userAuthenticated = NO;
        }
    };
}

Die report Methode erzeugt ein GKScore Objekt. Die benötigtet ID des Leaderboards bekommen wir übergeben. Das erzeugte Objekt packen wir in ein Array – man könnte auf diese Weise zum Beispiel mehrere Highscores auf einmal übermitteln.

Mit GKScore reportScore melden wir unsere Punkte. Auch hier gilt wieder: alles nach dem ^ ist ein Block. Der Aufruf erfolgt sobald das Melden der Punkte abgeschlossen ist. In der Zwischenzeit schreitet der „normale“ Code voran; es wird also nicht gewartet bis der Block aufgerufen wurde.

Wenn es keinen Fehler gab, prüfen wir ob LeaderboardHelper bereits einen Delegate zugewiesen bekommen hat. Falls dem so ist reichen wir die Information, dass wir erfolgreiche Punkte berichtet haben an unser Delegate weiter.

- (void) reportScore: (int64_t) score forLeaderboardID: (NSString*) identifier{
    
    if ([GKLocalPlayer localPlayer].isAuthenticated) {
        
        GKScore* scoreReporter = [[GKScore alloc] initWithLeaderboardIdentifier:identifier];
        scoreReporter.value = score;
        scoreReporter.context = 0;
        
        NSArray *scores = @[scoreReporter];
        [GKScore reportScores:scores withCompletionHandler:^(NSError *error) {
            if (error) {
                NSLog(@"LeaderboardHelper Report Score Error: %@", error);
            }else{
                NSLog(@"LeaderboardHelper: score report successful: %lli", score);
                if (self.leaderboardDelegate) {
                    [self.leaderboardDelegate leaderboardHelperReportedScore:self forIdentifier:identifier];
                }
            }
        }];
    }
}

Damit ist unser LeaderboardHelper erst einmal abgeschlossen. Die Authentifizierung des Nutzers sollte so früh wie möglich erfolgen – zum Beispiel in AppDelegate. Wir importieren unseren Helper und stoßen in application:didFinishLaunchingWithOptions: die Authentifizierung an.

#import "LeaderboardHelper.h"

@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{   
    [[LeaderboardHelper sharedInstance] authenticateLocalUser];  
    return YES;
}

Nun gehen wir in das Main_iPhone.storyboard
Den vorhandenen ViewController bestücken wir mit

  • zwei Labels
  • einem Textfield
  • und einem Button

Am Ende sollte das Ganze in etwas so aussehen:

InterfaceBuilder

Wir blenden noch den Assistant Editor ein und erstellen IBOutlets (wir ziehen die Storyboard item mit gedrückter ctrl-Taste in ViewController.h) für das TextField und das Status Label. Der Button bekommt eine IBAction. Außerdem müssen wir den LeaderboardHelper importieren und angeben, dass unser ViewController das Delegate Protokoll des Leaderboard Helpers unterstützt.

Bildschirmfoto 2014-07-10 um 08.42.21 Bildschirmfoto 2014-07-08 um 08.08.15

#import "LeaderboardHelper.h"

@interface ViewController : UIViewController

@property (strong, nonatomic) IBOutlet UILabel *statusLabel;
@property (strong, nonatomic) IBOutlet UITextField *textField;
- (IBAction)reportScoreAction:(id)sender;

Wir wechseln nun zu ViewController.m und ergänzen zunächst viewDidLoad. Der ViewController soll der Delegate des Leaderboard Helpers sein, deshalb weisen wir das entsprechend zu. Unser Textfield soll sofort angewählt sein, was wir mit becomeFirstResponder erreichen. Da wir das StatusLabel noch nicht brauchen wird es versteckt. Außerdem setzten wir das Keyboard des Textfeldes auf numerisch – unsere Highscore soll ja keine Buchstaben enthalten.

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // we are the delegate
    [LeaderboardHelper sharedInstance].leaderboardDelegate = self;
    
    // load kexboard on start
    [self.textField becomeFirstResponder];
    
    // hide status label
    self.statusLabel.hidden = YES;
    
    // keyboard with numbers only
    self.textField.keyboardType = UIKeyboardTypeNumberPad;
}

Wenn der Button vom Storyboard richtig mit der IBAction verknüpft ist, erfolgt der Aufruf von reportScore bei jeder Betätigung. Wir prüfen zunächst, ob der Nutzer angemeldet ist und überhaupt ein HighScore Wert eingegeben wurde. Anschließend interpretieren wir den Text der Eingabe als Integer Wert und übergeben ihn dem LeaderboardHelper zum Berichten. Wir entleeren noch das Textfeld, um dem Nutzer ein visuelles Feedback zu geben.

- (IBAction)reportScoreAction:(id)sender {
    
    if ([LeaderboardHelper sharedInstance].userAuthenticated && self.textField.text.length > 0) {
        // get the value of the textfield
        int highscore = [self.textField.text intValue];
        
        // report the score
        [[LeaderboardHelper sharedInstance] reportScore:highscore forLeaderboardID:@„noch.zu.definierende.leaderboardID“];
        
        // reset the textfield
        self.textField.text = @"";
    }else {
        NSLog(@"Benutzer ist nicht angemeldet oder keine Eingabe");
    }
}

Eventuell habt ihr schon bemerkt, dass Xcode die ganze Zeit rum meckert. Es bemängelt, dass die Delegate Methode „leaderboardHelperReportedScore:forIdentifier:“ noch nicht implementiert ist. Diese Methode rufen wir im Completion Handler von [GKScore reportScores:scores withCompletionHandler:^(NSError *error){}]; auf, sofern der Delegate gesetzt ist. Würden wir die Methode nicht implementieren, stürzt das Programm mit dem Aufruf ab. Das wollen wir nicht.

Bildschirmfoto 2014-07-08 um 08.11.15

-(void)leaderboardHelperReportedScore:(LeaderboardHelper *)leaderboardHelper forIdentifier:(NSString *)identifier
{
    self.statusLabel.hidden = NO;
    self.statusLabel.text = @"Highscore übermittelt";
}

Wir sind mit der Codeerstellung fertig und können das Programm nun starten. Die Anmeldung erfolgt, allerdings funktioniert das Melden der Punkte dennoch nicht.

Bildschirmfoto 2014-07-10 um 08.17.42

Der Grund ist recht einfach – wir haben noch gar kein Leaderboard erstellt. Und da ein Leaderboard immer an einer konkreten App bzw. App ID hängt müssen wir auch das tun. Loggen wir uns bei iTunes Connect ein und gehen zu „Manage my Apps“. Anschließend gehen wir zu „Add New App“

Bildschirmfoto 2014-07-09 um 16.40.07

Wir geben unsere „App Information“ ein. Da wir vorher noch keine Bundle ID erstellt haben nutzen wir den praktischen Link ganz unten „You can register a new Bundle ID here.“

Bildschirmfoto-2014-07-09-um-16.53

Unter Identifiers —> App IDs erstellen wir uns die passende App ID. Ihr könnt als Namen natürlich wählen was ihr möchtet. Als App ID hat sich eine umgekehrte URL bewährt. So etwas wie de.ioscampus.ioscampustutorialapp

Bildschirmfoto 2014-07-09 um 16.43.42 Bildschirmfoto-2014-07-09-um-16.44

Zurück bei App Information könnt ihr nach kurzer Zeit die neu erstellte Bundle ID zuweisen. Im nachfolgenden Formular müsst Ihr weitere Informationen, App Beschreibungen und Screenshots hochladen. Da viele Felder Pflichtangaben sind ist der Schritt besonders nervig.Bildschirmfoto 2014-07-09 um 16.58.21

Ich habe euch hier ein paar Screenshots zum Hochladen erstellt. In dem Archiv ist alles enthalten was Ihr benötigt.

Grafik eines Zip Ordners

Endlich sind wir mit der Erstellung einer App fertig und können zu „Manage Game Center“ navigieren.

Bildschirmfoto 2014-07-10 um 06.22.21

Wir wählen „Add Leaderboard“ und nehmen „Enable for Single Game“ und „Single Leaderboard“. Man kann mehrere Leaderboards zu einer Gesamtwertung kombinieren. Außerdem können sich mehrere Apps ein Leaderboard teilen. Spannende Möglichkeiten – das machen wir vielleicht ein anderes mal. Das einfach Leaderboard soll für heute genügen.

Bildschirmfoto 2014-07-10 um 06.23.09

Bildschirmfoto 2014-07-10 um 06.22.40

Bildschirmfoto 2014-07-10 um 06.23.15

Anschließend konfigurieren wir unser Leaderboard.

Bildschirmfoto-2014-07-10-um-06.24

Die definierte Leaderboard ID muss noch in unseren Quellcode nachgetragen werden. In reportScoreAction ändern wir @„noch.zu.definierende.leaderboardID“ in die gewählte ID.

Wenn wir nun die App starten und einen Highscore melden sehen wir den Erfolg.

iOS Simulator Bildschirmfoto 18.07.2014 18.21.00

Sehr gut. Bis zum nächsten mal.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.