Game Center Tutorial – How to set up a Multiplayer Game like SNOWGRE

First things first: We just released an update of our MULTIPLAYER App called “SNOWGRE“, available for FREE on the App Store: https://itunes.apple.com/us/app/snowgre/id560450513?mt=8

SNOWGRE_multiplayer

SNOWGRE – Multiplayer game using Game Center

Please have a look, download it for free and consider buying some extra lives and unlock the multiplayer feature to play against people from all over the world! Play agains me => my nickname is “GABEHOE” or against my co-worker Adrian => “ADIMAN84″ :D
We’d love to hear suggestions and receive feedbacks from you… and compete with you! Hahahah…

On this blog post I am going to show you how to use Game Center (Game Kit Framework) to make an amazing Multiplayer Game like SNOWGRE… well, not the whole game, but at least I will show you how to code the multiplayer stuff! ;)

Advertisement:

white,pages,yellow,tracker,search,find,gps,mobile,411,ringtone,business,911,likes,follower,locator

The first iPhone App ever that finds out who’s calling you before you answer the phone.


 

First, we have to diferentiate what happens in the frontend (all the stuff you “see” with your eyes) and in the backend (all the connection stuff and data that is being exchanged between the players).

So this is the flow for the Player:

  1. Let the user start a multiplayer session (make a fancy Button => start Multiplayer Game)
  2. Present the Game Center ViewController to find oponents or invite a friend - matchmaking!
  3. Once Game Center has found enough players for a match, we present the game scene

let’s enable Game Center for our App:

Preparations

  1. Login into iTunes Connect
  2. Click on “Manage Your Apps”
  3. Click on the App Icon
  4. Follow the steps described on the screenshot below:
Game Center Activation iTunes Connect

Game Center Activation – iTunes Connect

 

Now we have enabled and activated Game Center for our Game. Let’s go to XCode…

  • Add the GameKit Framework into your project => select the Project (top left of Groups & Files), select the “Build Phases” Tab on the right, expand the “Link Binary with Libraries” section and press the “+”-button. Then, select the “GameKit.framework” and click “add”.
Game Kit Framework

Add GameKit.framework to your project

 

Authenticate the player

We have now everything preparated to begin with the implementation. First we must know first a few things about authentication. Basically whenever your App starts up, we have to authenticate (or check) the player in Game Center. If the user is already authenticated, there appears a welcome message like “Welcome back!”. If the user is not authenticated, a ViewController will show up asking for a username & password. But that’s not everything… what if the user logs out and your App is running? Fortunately there is an “authentication changed” notification so that we can immediately react and invite the user to log in again.

Let’s dig into some codeezzzz…

We need a GameCenterManager singleton, that will be responsible for all Game Center action throughout our game. Make a new class, a subclass of NSObject and name it “GameCenterManager”:

GameCenterManager.h

#import <Foundation/Foundation.h>
#import <GameKit/GameKit.h>

@protocol GameCenterManagerDelegate 

- (void)matchStarted;
- (void)matchEnded;
- (void)match:(GKMatch *)match didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID;
- (void)inviteReceived;

@optional
- (void) processGameCenterAuth: (NSError*) error;
- (void) mappedPlayerIDToPlayer: (GKPlayer*) player error: (NSError*) error;
@end

@interface GameCenterManager : NSObject 
{
    id  delegate;

    BOOL userAuthenticated;

    UIViewController *presentingViewController;
    GKMatch *match;
    BOOL matchStarted;

    NSMutableDictionary *playersDict;

    GKInvite *pendingInvite;
    NSArray *pendingPlayersToInvite;
}

@property (retain) UIViewController *presentingViewController;
@property (retain) GKMatch *match;
@property (nonatomic, assign)  id  delegate;
@property (retain) NSMutableDictionary *playersDict;

@property (retain) GKInvite *pendingInvite;
@property (retain) NSArray *pendingPlayersToInvite;

- (void)findMatchWithMinPlayers:(int)minPlayers maxPlayers:(int)maxPlayers viewController:(UIViewController *)viewController delegate:(id)theDelegate;
- (void) authenticateLocalUser; 
- (void) mapPlayerIDtoPlayer: (NSString*) playerID;

+ (BOOL) isGameCenterAvailable;

@end

 

GameCenterManager.m

#import "GameCenterManager.h"
#import <GameKit/GameKit.h>
#import "RootViewController.h"

@implementation GameCenterManager

@synthesize delegate;
@synthesize presentingViewController;
@synthesize match;
@synthesize playersDict;
@synthesize pendingInvite;
@synthesize pendingPlayersToInvite;

- (id) init
{
    self = [super init];
    if(self!= NULL)
    {
        // when we init, we check if Game Center is available
        if([GameCenterManager isGameCenterAvailable]) {

            // this is very important... since we must know if the user logs in/out
            NSNotificationCenter *nc =
            [NSNotificationCenter defaultCenter];
            [nc addObserver:self
                   selector:@selector(authenticationChanged)
                       name:GKPlayerAuthenticationDidChangeNotificationName
                     object:nil];
        }
    }
    return self;
}

- (void) dealloc
{
    [super dealloc];
}

- (void)authenticationChanged {

    if ([GKLocalPlayer localPlayer].isAuthenticated && !userAuthenticated) {

        userAuthenticated = TRUE;

        [GKMatchmaker sharedMatchmaker].inviteHandler = ^(GKInvite *acceptedInvite, NSArray *playersToInvite) {

            self.pendingInvite = acceptedInvite;
            self.pendingPlayersToInvite = playersToInvite;
            [delegate inviteReceived];
        };

    } else if (![GKLocalPlayer localPlayer].isAuthenticated && userAuthenticated) {

        userAuthenticated = FALSE;
    }
}

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

+ (BOOL) isGameCenterAvailable
{
    // check for presence of GKLocalPlayer API
    Class gcClass = (NSClassFromString(@"GKLocalPlayer"));

    // check if the device is 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) authenticateLocalUser
{
    if([GKLocalPlayer localPlayer].authenticated == NO)
    {
        [[GKLocalPlayer localPlayer] authenticateWithCompletionHandler:^(NSError *error) 
        {
            [self callDelegateOnMainThread: @selector(processGameCenterAuth:) withArg: NULL error: error];
        }];
    }
}

#pragma mark -
#pragma mark find match with min players
// Add new method, right after authenticateLocalUser
- (void)findMatchWithMinPlayers:(int)minPlayers maxPlayers:(int)maxPlayers viewController:(UIViewController *)viewController delegate:(id<GameCenterManagerDelegate>)theDelegate {

    if (![GameCenterManager isGameCenterAvailable]) return;

    matchStarted = NO;
    self.match = nil;
    self.presentingViewController = viewController;
    delegate = theDelegate;

    if (pendingInvite != nil) {

        [presentingViewController dismissModalViewControllerAnimated:NO];
        GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithInvite:pendingInvite] autorelease];
        mmvc.matchmakerDelegate = self;
        [presentingViewController presentModalViewController:mmvc animated:YES];

        self.pendingInvite = nil;
        self.pendingPlayersToInvite = nil;

    } else {

        // with minPlayers/maxPlayers we define how many players our multiplayer 
        // game may or must have
        [presentingViewController dismissModalViewControllerAnimated:NO];
        GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
        request.minPlayers = minPlayers;
        request.maxPlayers = maxPlayers;
        request.playersToInvite = pendingPlayersToInvite;

        GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithMatchRequest:request] autorelease];
        mmvc.matchmakerDelegate = self;

        [presentingViewController presentModalViewController:mmvc animated:YES];

        self.pendingInvite = nil;
        self.pendingPlayersToInvite = nil;
    }
}

#pragma mark -
#pragma mark get players info
// Add new method after authenticationChanged
- (void)lookupPlayers {

    NSLog(@"Looking up %d players...", match.playerIDs.count);
    [GKPlayer loadPlayersForIdentifiers:match.playerIDs withCompletionHandler:^(NSArray *players, NSError *error) {

        if (error != nil) {
            NSLog(@"Error retrieving player info: %@", error.localizedDescription);
            matchStarted = NO;
            [delegate matchEnded];
        } else {

            // Populate players dict
            self.playersDict = [NSMutableDictionary dictionaryWithCapacity:players.count];
            for (GKPlayer *player in players) {
                NSLog(@"Found player: %@", player.alias);
                [[RootViewController sharedInstance] setPlayer2Alias:player.alias];
                [playersDict setObject:player forKey:player.playerID];
            }

            // Notify delegate match can begin
            matchStarted = YES;
            [delegate matchStarted];
        }
    }];

}

#pragma mark GKMatchmakerViewControllerDelegate
// The user has cancelled matchmaking
- (void)matchmakerViewControllerWasCancelled:(GKMatchmakerViewController *)viewController {

    [presentingViewController dismissModalViewControllerAnimated:YES];
}

// Matchmaking has failed with an error
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFailWithError:(NSError *)error {

    [presentingViewController dismissModalViewControllerAnimated:YES];
    NSLog(@"Error finding match: %@", error.localizedDescription);

    UIAlertView *resetAlert = [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Error", @"") message:error.localizedDescription delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", @"") otherButtonTitles:nil] autorelease];
    [resetAlert show];
}

// A peer-to-peer match has been found, the game should start
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFindMatch:(GKMatch *)theMatch {

    [presentingViewController dismissModalViewControllerAnimated:YES];
    self.match = theMatch;
    match.delegate = self;
    if (!matchStarted && match.expectedPlayerCount == 0) {

        // Add inside matchmakerViewController:didFindMatch, right after @"Ready to start match!":
        [self lookupPlayers];
    }
}

#pragma mark GKMatchDelegate

// The match received data sent from the player.
- (void)match:(GKMatch *)theMatch didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID {
    if (match != theMatch) return;

    [delegate match:theMatch didReceiveData:data fromPlayer:playerID];
}

// The player state changed (eg. connected or disconnected)
- (void)match:(GKMatch *)theMatch player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state {

    if (match != theMatch) return;

    switch (state) {
        case GKPlayerStateConnected:
            // handle a new player connection.
            NSLog(@"Player connected!");

            if (!matchStarted && theMatch.expectedPlayerCount == 0) {

                NSLog(@"Ready to start match!");
                [self lookupPlayers];
            }

            break;
        case GKPlayerStateDisconnected:
            // a player just disconnected.
            NSLog(@"Player disconnected!");
            matchStarted = NO;
            [delegate matchEnded];
            break;
    }
}

// The match was unable to connect with the player due to an error.
- (void)match:(GKMatch *)theMatch connectionWithPlayerFailed:(NSString *)playerID withError:(NSError *)error {

    if (match != theMatch) return;

    NSLog(@"Failed to connect to player with error: %@", error.localizedDescription);
    matchStarted = NO;
    [delegate matchEnded];
}

// The match was unable to be established with any players due to an error.
- (void)match:(GKMatch *)theMatch didFailWithError:(NSError *)error {

    if (match != theMatch) return;

    NSLog(@"Match failed with error: %@", error.localizedDescription);
    matchStarted = NO;
    [delegate matchEnded];
}

- (void) reloadHighScoresForCategory: (NSString*) category
{
    GKLeaderboard* leaderBoard= [[[GKLeaderboard alloc] init] autorelease];
    leaderBoard.category= category;
    leaderBoard.timeScope= GKLeaderboardTimeScopeAllTime;
    leaderBoard.range= NSMakeRange(1, 1);

    [leaderBoard loadScoresWithCompletionHandler:  ^(NSArray *scores, NSError *error)
    {
        [self callDelegateOnMainThread: @selector(reloadScoresComplete:error:) withArg: leaderBoard error: error];
    }];
}

- (void) mapPlayerIDtoPlayer: (NSString*) playerID
{
    [GKPlayer loadPlayersForIdentifiers: [NSArray arrayWithObject: playerID] withCompletionHandler:^(NSArray *playerArray, NSError *error)
    {
        GKPlayer* player= NULL;
        for (GKPlayer* tempPlayer in playerArray)
        {
            if([tempPlayer.playerID isEqualToString: playerID])
            {
                player= tempPlayer;
                break;
            }
        }
        [self callDelegateOnMainThread: @selector(mappedPlayerIDToPlayer:error:) withArg: player error: error];
    }];

}

@end

 

So this is our GameCenterManager. I used basically the same code from as Apple suggests here: GameCenterManager. But removed a few methods like “submitAchievements”, “resetAchievements”, etc. since this is not need here for the scope of this blog post. The most important method for us is “findMatchWithMinPlayers”. This method is responsible for finding an oponent player in the same network or somewhere else in the Game Center network.

Game Center Real-Time Matchmaking

Game Center Real-Time Matchmaking

Another interesting method is this one:

 // The match received data sent from the player.
- (void)match:(GKMatch *)theMatch didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID {
    if (match != theMatch) return;

    [delegate match:theMatch didReceiveData:data fromPlayer:playerID];
}

 

Since we define a delegate (our “RootViewController” – more about this one later on this post), our GameCenterManager sends the data to him whenever he receives data from a player (playerID). Like this we always know when a player changes position or shoots from his gun, etc… got it?

Our delegate: RootViewController

This ViewController (can be any other ViewController that you decide to be the delegate of our GameCenterManager) decides what to do with the information we get from our oponent(s). The GameCenterManager tells our RootViewController following…

  1. We found an oponent (or more)! His name is “player2″
  2. We are ready to start the match!
  3. Stay tuned, I will send you his positions and his states!
  4. If the player2 (or other players) loose connection or abort the game, or win the game, I will notify you!

OK… now we need to handle the data we get from our GameCenterManager as following: create (if not yet existing) a ViewController (subclass of UIViewController in our case – not mandatory! We have additional code in our RootViewController… that’s why) that will be the delegate of the GameCenterManager:

RootViewController.h

#import <UIKit/UIKit.h>
#import <GameKit/GameKit.h>
#import "GameCenterManager.h"
#import "Constants.h"

@interface RootViewController : UIViewController <GameCenterManagerDelegate> {

    GameCenterManager *gameCenterManager;
    BOOL userAuthenticated;
    NSString *oponentPlayerID;
    GameState gameState;
    NSString *player2Alias;
}

@property (nonatomic, retain) GameCenterManager *gameCenterManager;
@property (nonatomic, retain) NSString *player2Alias;
@property (readonly) GameState gameState;

- (void)sendMoveWithHeroPositions:(HeroInfo *)heroInfo;
- (void)sendDisconnection;
- (void)sendGameOver:(BOOL)oponentHasWon;
- (void)setupMultiplayerGame;

+ (RootViewController *)sharedInstance;

@end

 

RootViewController.m

#import "RootViewController.h"
#import "AppDelegate.h"

static RootViewController *staticRootVC = nil;

@implementation RootViewController

@synthesize gameCenterManager;
@synthesize gameState;
@synthesize player2Alias;

// The designated initializer.  Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
    // Custom initialization

        staticRootVC = self;

        if ([GameCenterManager isGameCenterAvailable]) {

            // init our GameCenterManager and immediately try to authenticate the user
            self.gameCenterManager = [[[GameCenterManager alloc] init] autorelease];
            [self.gameCenterManager setDelegate:self];
            [self.gameCenterManager authenticateLocalUser];

        } else {

            // The current device does not support Game Center. Show a fancy message.
        }
    }

    return self;
 }

#pragma mark - 
#pragma mark multiplayer stuff

// this is the method that you call when the player hits the button "START MULTIPLAYER GAME"
-(void)setupMultiplayerGame {

    [self setGameState:kGameStateWaitingForPlayersToStartMatch];

    // We command our GameCenterManager to find a match for 2 players => a screen is presented
    // so the user can choose if he wants to invite a friend or just find anybody to play
    // with all over the world...
    // MAIN_GAME_VIEWCONTROLLER is a ViewController that is responsible to show the 
    // GKMatchmakerViewController modally... in cocos2d this could be: [CCDirector sharedDirector]
    [gameCenterManager findMatchWithMinPlayers:2 maxPlayers:2 viewController:MAIN_GAME_VIEWCONTROLLER delegate:self];
}

// HeroInfo is a struct that hold information like position, state, etc. about the player's Hero!
// It is good practice to send structs over the net, since they are small comparing to 
// NSStrings, etc. we will use always structs if possible.
- (void)sendMoveWithHeroPositions:(HeroInfo *)heroInfo {

    NSData *data = [NSData dataWithBytes:heroInfo length:sizeof(HeroInfo)*HERO_INFO_PACKAGESIZE];
    [self sendData:data];
}

-(void)sendDisconnection {

    MessageDisconnection myMessage;
    myMessage.message.messageType = kMessageTypeDisconnection;
    NSData *data = [NSData dataWithBytes:&myMessage length:sizeof(MessageDisconnection)];
    [self sendData:data];
}

// if we reach the goal first, send this notification to the oponent... if we did not receive
// yet the same message from our oponent!
- (void)sendGameOver:(BOOL)didWinTheMatch {

    MessageGameIsOver myMessage;
    myMessage.message.messageType = kMessageTypeGameIsOver;
    myMessage.didWinTheMatch = didWinTheMatch;
    NSData *data = [NSData dataWithBytes:&myMessage length:sizeof(MessageGameIsOver)];
    [self sendData:data];
}

// I did achieve better results with "GKMatchSendDataReliable" instead of "GKMatchSendDataUnreliable"
// although "GKMatchSendDataUnreliable" would use the UDP afaik
- (void)sendData:(NSData *)data {

    NSError *error;
    BOOL success = [gameCenterManager.match sendDataToAllPlayers:data withDataMode:GKMatchSendDataReliable error:&error];

    if (!success) {
       // try again, or review your code or wait for disconnection :) 
    }
}

- (void)sendGameHasBegun {

    MessageGameHasBegun myMessage;
    myMessage.message.messageType = kMessageTypeGameHasBegun;
    NSData *data = [NSData dataWithBytes:&myMessage length:sizeof(MessageGameHasBegun)];
    [self sendData:data];
}

// this method is always called twice 
- (void)startGame {

    if (gameState == kGameStateWaitingForOponentToStart) {

        [self setGameState:kGameStateIsReadyToStartGame];
        [self sendGameHasBegun];

        [self reallyStartMultiplayerSession];
    }
}

- (void)inviteReceived {

    [self setupMultiplayerGame];
}

- (void)matchStarted {
    [self setGameState:kGameStateWaitingForOponentToStart];
    [self startGame];
}

- (void)matchEnded {

    NSLog(@"Match ended because of disconnection or anything else... check above our GameCenterManager... handle!");
}

// this method receives data from all players, in our game only from one oponent
- (void)match:(GKMatch *)match didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID {

    // Imagine we have a multiplayer game with 4 players... somehow you have to differentiate
    // the data you receive... are we getting new positions from player2 or player3 or player4?
    // so the three following lines we don't need in OUR example, but I let it there as a hint
    // for you if you plan to make a multiplayer game with more than 2 players in total.
    if (oponentPlayerID == nil) {
        oponentPlayerID = [playerID retain];
    }

    // there are other ways to check if we receive a game status message or a hero position
    // message... but this works :)
    if ([data length] == sizeof(HeroInfo) * HERO_INFO_PACKAGESIZE) {

        // in our game we have to simulate the oponent and all his actions... in our case it 
        // is the Hero2 object. So we send the info to that object, so that we can put the 
        // Hero2 into the right position on the screen and into the right state (stunned, 
        // hitting, burping, etc.)
        HeroInfo *messageHero = (HeroInfo *)[data bytes];
        [[Hero2 sharedInstance] setHeroInfo:messageHero]; 
    }
    else {

        // we received a game state message... something happened
        HeroMessage *myMessage = (HeroMessage *) [data bytes];

        if (myMessage->messageType == kMessageTypeGameHasBegun) {

            [self setGameState:kGameStateIsReadyToStartGame];
            [self reallyStartMultiplayerSession];
        }
        else if (myMessage->messageType == kMessageTypeDisconnection) {

             // our GameCenterManager just told us that our oponent was disconnected fro
             // from the match... maybe he got a phone call, or his phone ran out of batteries,
             // or whatever... react! You can close the match or tell the user that he has
             // won or just call [self setupMultiplayerGame] again...
        }
        else if (myMessage->messageType == kMessageTypeGameIsOver) {

            if (myMessage->oponentHasWon) {

                // we received the message from the oponent, that he has won the match!
            }
            else {

                // we received the message from the oponent, that he has lost the game
                // due to an unknown (yet) reason.
             }
        }
    }
}

-(void)reallyStartMultiplayerSession {

    // YOUR_GAME is responsible to show the game
    [YOUR_GAME startMultiplayerSession];
}

- (void)viewDidLoad {

   [super viewDidLoad];
}

- (NSUInteger) supportedInterfaceOrientations {

    //Because your app is only landscape, your view controller for the view in your
    // popover needs to support only landscape
    return UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {

    return UIInterfaceOrientationIsLandscape(toInterfaceOrientation);
}

-(BOOL)shouldAutorotate {

    return YES;
}
- (void)didReceiveMemoryWarning {

    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (void)dealloc {
    [super dealloc];
}

+(RootViewController *)sharedInstance {

    return staticRootVC;
}

@end

 

HANG ON! We’re almost finished… there is another headerfile where we define our message-structures called Constants.h. Before I show you that, let me explain some functions in RootViewController first. The method – (void)setupMultiplayerGame; tells our GameCenterManager that we want to play a multiplayer game with a friend or somebody in the world. The GameCenterManager presents then a GKMatchmakerViewController modally where the user can choose between looking for an oponent nearby or worldwide. Once Game Center has found an oponent, it tells our RootViewController that we are ready to play.

OK, let’s see what we need in Constants.h…

Constants.h

// This here defines the multiplyer of size of package we send each time to our oponent...
// For example in a game, you define 60fps or sometimes 30fps... so it's not wise to send 
// 30 or 60 times a second a Package to the oponent!!! Obviously, right...? So don't forget
// to build up a buffer or some sort of technology to cope with this problem, otherwise the
// hero2 will not update smoothly on your screen...
#define HERO_INFO_PACKAGESIZE 3
typedef enum {
    kGameStateWaitingForPlayersToStartMatch = 0,
    kGameStateWaitingForOponentToStart,
    kGameStateIsReadyToStartGame
} GameState;

typedef enum {
    kMessageTypeGameHasBegun = 0,
    kMessageTypeGameIsOver,
    kMessageTypeDisconnection
} MessageType;

// you can define here whatever type of status you need!
typedef enum {
    kHerostatusBlue = 0,
    kHerostatusStun,
    kHerostatusJump,
    kHerostatusHit
} HeroStatus;

typedef struct {
    MessageType messageType;
} HeroMessage;

// This is very important. In our game we send the position (x/y) and the angle of our hero
// plus the status (blue, stun, jump, hit, etc... according to your needs, you can modify it)
typedef struct{
    MessageType messageType;
    int status;
    float x;
    float y;
    float angle;
} HeroInfo;

typedef struct {
    HeroMessage message;
} MessageGameHasBegun;

typedef struct {
    HeroMessage message;
} MessageDisconnection;

typedef struct {
    HeroMessage message;
    BOOL didWinTheMatch;
} MessageGameIsOver;

 

That’s it… you see, I have shown you a nice way to set up a multiplayer game with GameCenter. You can use the code above and modify it if you want, according to your needs.

I know this tutorial was a bit kind of advanced stuff, so if you wish more explanation on specific topics mentioned above, just leave a comment.

HAPPY CODING! :)

 

7 comments

  1. Michel says:

    Hi,
    I tried to implement your code in a project but I get an error on the [self setGameState… statement. Is it possible that a section is missing from the code on your tutorial?

    Thanks very much for this fine tutorial and for your help.

    Michel Desjardins

  2. Jake says:

    Thank you, I have been looking for some help. I will download your app.

  3. Mike says:

    Have you tried to copy the code from your site and paste on Xcode 5?
    Man, you have some problem on your site… try that and see.

  4. Hugo says:

    Hey,
    Thanks for posting this piece of code :)

    I’m pretty sure that I’ll be able to manage sending/receive and updating the UI with the data received, but one thing I’m not quite able to determine is when to actually start the match : when a player Connects or when didFindMatch ?

    Hugo

  5. ahmad says:

    Can you send small project for me thats will helpful

Leave a Reply to Mike Cancel reply

Your email address will not be published. Required fields are marked *


*