Game Center - Sending and receiving data

Josh Kahane picture Josh Kahane · Jan 1, 2011 · Viewed 8.2k times · Source

EDIT: I have made a clean, new project, but still can't get it working. Please download it, there is a little code to look at and probably easy for a professional or anyone remotely experience to see whats I am doing wrong. Just trying to send that integer.

http://www.2shared.com/file/fPOCLlg5/gkTest.html

Hi

I am trying to implement Game Center multiplayer in my iphone game and having trouble understanding the samples I have at hand in the Apple Docs and from third parties concerning sending and receiving data.

Could someone please explain the code samples in the Official Apple docs here please: http://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/GameKit_Guide/MatchesandVoice/MatchesandVoice.html#//apple_ref/doc/uid/TP40008304-CH10-SW4

Or help me figure out this sample code I was supplied with. Its a prebuilt class, made to handle all the game center tasks and a sample from it for sending and receiving data would be this:

- (void) sendPosition
{
    NSError *error;
    PositionPacket msg;
    msg.messageKind = PositionMessage;
    msg.x = currentPosition.x;
    msg.y = currentPosition.y;
    NSData *packet = [NSData dataWithBytes:&msg length:sizeof(PositionPacket)];
    [match sendDataToAllPlayers: packet withDataMode: GKMatchSendDataUnreliable error:&error];
    if (error != nil)
    {
        // handle the error
    }
}

And receiving:

- (void)match:(GKMatch *)match didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID
{
    Packet *p = (Packet*)[data bytes];
    if (p.messageKind == PositionMessage)
        // handle a position message.
}

My big question about this code form the official docs is:

Where does PositionPacket/Packet come from? And assuming when you want to send/receive data you call them like so:

[self sendPosition];

or

[self match:(GKMatch *)match didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID];

What do I enter as the match, data and playerID?

E.g. I have an int named 'score' but is there not a special key I need to use something?

Answer

ipmcc picture ipmcc · Jan 1, 2011

In this example, the PositionPacket is just a struct. The following line then puts that struct into an NSData which is just a "byte bucket" object.

NSData *packet = [NSData dataWithBytes: &msg length: sizeof(PositionPacket)];

So if you just wanted to send an int score, you could have a sendScore method that looked like this:

- (void) sendScore
{
    NSError *error;
    int myScore = self.score;
    NSData *packet = [NSData dataWithBytes:&myScore length:sizeof(myScore)];
    [match sendDataToAllPlayers: packet withDataMode: GKMatchSendDataUnreliable error: &error];
    if (error != nil)
    {
        // handle the error
    }
}

Typically, you'll want a struct so that there's some additional information that lets the receivers know what kind of data it is. In the example, that would have been the purpose of this line:

msg.messageKind = PositionMessage;

In general, you can send anything you want encapsulated in an NSData object, since it's just a byte bucket. You can send primitive types like ints, or structs as in the example, or even NSObjects (as long as they implement NSCoding). You should read up on NSKeyedArchiver, NSCoding, and NSData for more information on sending and receiving NSObjects in this way. Here is Apple's reference document on Archving.

As for receiving, YOU don't call the method, it gets called ON you by the Kit. It's what's called a "delegate method" (in Cocoa parlance) or a "callback method." You can think of it like a "phone call" that your app can receive asynchronously. By implementing a method with the signature:

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

...you are saying "I can receive this kind of phone call." So when the GameKit receives data on your behalf from another player, it will see that you want to receive callbacks of that kind and will then call that method -- then it's up to you to update your internal application state based on what is received.

To continue with this example, if you had sent the simple "nothing but an integer" message described above, you might implement that method like this:

- (void)match:(GKMatch *)match didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID
{
    int* receivedScorePtr = (int*)[data bytes];
    int receivedScore = *receivedScorePtr;
    [self updateScore: received forPlayer: playerID];
}

That is, of course, assuming that you have a method called updateScore:forPlayer: that would update a table of scores.

You can find a more general discussion/explanation of how delegates and delegate methods work at this blog entry: http://mohrt.blogspot.com/2010/01/cocoa-and-delegates.html

ADDED: Using code the asker posted, I made a few modifications and produced a version that "works" for this bare bones use case. Working Version of Test App To Send One Integer Through GameCenter I make no claims about the quality of the code, or its suitability for, well, anything at all. I didn't write 99.9% of it - please don't take my posting of it here as an endorsement of anything that appears in it.

One lesson learned (that I didn't know, so I'm putting here in hopes that it helps others) is that you can't use the Matchmaking service with the simulator. This means that you need two development-provisioned iOS devices in order to test this scenario, and likely, for non-trivial programs, two development machines to debug both devices simultaneously. This problem cost me the most time while figuring this out.