I cannot establish a connection between two players using auto-match and GKMatchmakerViewController.
The behavior is this:
didFindMatch is called
expectedPlayerCount is not zero (always 1)
didChangeState is never called
after an extended period of time the player disconnected is received.
Does anyone have a solution to this problem?
Thanks!
In order to get the correct number of players connected, make sure you setup your GKMatchRequest properly.
In this example, I have a game that only works if there are two players connected, so I set the min & max players count to 2:
GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
request.minPlayers = 2;
request.maxPlayers = 2;
GKMatchmakerViewController* mmvc = [[[GKMatchmakerViewController alloc] initWithMatchRequest:request] autorelease];
mmvc.matchmakerDelegate = self;
Then, everything you noted works as expected.
EDIT:
After reading your question again, also make sure you have retained the GKMatch object given to you by:
-(void) matchmakerViewController:(GKMatchmakerViewController*)viewController didFindMatch:(GKMatch*)match;
And of course, you have to set the delegate of this match object:
//Done!
-(void) matchmakerViewController:(GKMatchmakerViewController*)viewController didFindMatch:(GKMatch*)match{
[self dismissModalViewController];
[self setCurrentMatch:match];
match.delegate = self;
if (!matchStarted && match.expectedPlayerCount == 0){
matchStarted = YES;
[delegate onMatchStart];
self.handlerVoice = [OnlineHandlerVoice handlerWithMatch:currentMatch];
}
}
Related
I have been developing a game which allows for multiplayer matches. I had previous tested the multiplayer invitations and they had all worked. Sending a request from one device displayed a banner on the other and if the invite was accepted the game started.
Just before submitting the app, two nights ago, I tested this functionality again only to find that it has stopped working.
- (void)authenticateLocalUser:(UIViewController *)viewController :(id<GCHelperDelegate>)theDelegate
{
delegate = theDelegate;
self.presentingViewController = viewController;
if (!gameCenterAvailable) {
// Game Center is not available.
userAuthenticated = FALSE;
}
else{
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
/*
The authenticateWithCompletionHandler method is like all completion handler methods and runs a block
of code after completing its task. The difference with this method is that it does not release the
completion handler after calling it. Whenever your application returns to the foreground after
running in the background, Game Kit re-authenticates the user and calls the retained completion
handler. This means the authenticateWithCompletionHandler: method only needs to be called once each
time your application is launched. This is the reason the sample authenticates in the application
delegate's application:didFinishLaunchingWithOptions: method instead of in the view controller's
viewDidLoad method.
Remember this call returns immediately, before the user is authenticated. This is because it uses
Grand Central Dispatch to call the block asynchronously once authentication completes.
*/
//ios 6
[localPlayer setAuthenticateHandler:(^(UIViewController* viewcontroller, NSError *error) {
if (viewcontroller != nil){
userAuthenticated = FALSE;
[self.presentingViewController presentViewController: viewcontroller animated: YES completion:nil];
}
else if (localPlayer.isAuthenticated){
// Enable Game Center Functionality
userAuthenticated = TRUE;
[self checkForInvite:self.presentingViewController :delegate];
if (! self.currentPlayerID || ! [self.currentPlayerID isEqualToString:localPlayer.playerID]) {
// Current playerID has changed. Create/Load a game state around the new user.
self.currentPlayerID = localPlayer.playerID;
// get friends of local player
[localPlayer loadFriendsWithCompletionHandler:^(NSArray *friends, NSError *error) {
if (friends != nil)
{
[self loadPlayerData: friends];
}
}];
}
}
else{
userAuthenticated = FALSE;
}
[scoreHandler setGameCentreAvailable:userAuthenticated];
})];
}
}
- (void)checkForInvite :(UIViewController *)viewController :(id<GCHelperDelegate>)theDelegate
{
delegate = theDelegate;
self.presentingViewController = viewController;
NSLog(#"Invite handler installed");
[GKMatchmaker sharedMatchmaker].inviteHandler = ^(GKInvite *acceptedInvite, NSArray *playersToInvite) {
// Insert application-specific code here to clean up any games in progress.
if (acceptedInvite){
NSLog(#"Accepted");
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithInvite:acceptedInvite] autorelease];
mmvc.matchmakerDelegate = self;
[viewController presentViewController: mmvc animated: YES completion:nil];
} else if (playersToInvite) {
NSLog(#"Match Request");
GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
request.minPlayers = 2;
request.maxPlayers = 2;
request.playersToInvite = playersToInvite;
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithMatchRequest:request] autorelease];
mmvc.matchmakerDelegate = self;
[viewController presentViewController: mmvc animated: YES completion:nil];
}
};
}
The debug window in xcode shows the following:
2013-03-27 18:06:20.112 MyApp[791:907] Authentication changed: player authenticated.
2013-03-27 18:06:21.219 MyApp[791:907] Invite handler installed
Mar 27 18:06:21 Neils-iPhone MyApp[791] <Notice>: 18:06:21.356712 com.apple.GameKitServices: -[GKDiscoveryManager startAdvertisingLocalPlayer:discoveryInfo:]: I am [<nil>] [7989F444CF2BDA83] discoveryInfo [{
e = 2;
h = A42FD7FD;
}]
Is the "I am []..." significant in the lines above?
I have even downloaded and run the tutorial from Ray Wenderlich's site for creating a multiplayer game and tried that. That exhibits the same issues, unless it is running in the foreground on both devices. My app does not display invitation requests even if running in the foreground.
Has anyone else experienced this problem or have any ideas what is going on? authenticateLocalUser is called from applicationDidFinishLaunching
The only way I could make invites-by-name work is by going to Settings/Notifications/Game Center and making Game Center display Alerts, not Banners.
If you have GC display alerts, you get a popup box like this:
This dialog acts like a big parent. If the user hits Accept, then your [GKMatchmaker sharedMatchmaker].inviteHandler gets invoked.
If the user hits Decline, then your game never knows that he was invited to any party ever. User hitting Decline means the parent rips up the invitation and never tells his child he got invited to a game at all.
This is the only way I could get invite-by-name to work.
So I'm working with multiplayer for the first time and I'm confused about the whole minplayer/maxplayer options. When I set minplayer=2 and maxplayer=4 and test the code, it connects 2 players just fine, but jumps directly into the game scene without waiting for players 3-4. How do I keep the code from progressing to the main game scene before all the slots are filled? The code works fine if I set minPlayers=maxPlayers. I know match.expectedPlayerCount==0 is supposed to fire once minPlayers is satisfied, but it isn't waiting at all for additional players to join. What am I missing here?
GKMatchRequest * matchRequest = [[[GKMatchRequest alloc] init] autorelease];
matchRequest.minPlayers = 2;
matchRequest.maxPlayers = 4;
gameCenterManager.matchController = [[GKMatchmakerViewController alloc] initWithMatchRequest:matchRequest];
gameCenterManager.matchController.matchmakerDelegate = self;
AppDelegate * delegate = (AppDelegate *) [UIApplication sharedApplication].delegate;
[delegate.viewController presentViewController:gameCenterManager.matchController animated:YES completion:nil];
Find Match Code
-(void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFindMatch:(GKMatch *)match
{
TXGameCenterManager *gameCenterManager = [TXGameCenterManager sharedTXGameCenterManager];
gameCenterManager.multiplayerMatch = match;
// The delegate of the match is HelloWorldLayer
gameCenterManager.multiplayerMatch.delegate = self;
AppDelegate * delegate = (AppDelegate *) [UIApplication sharedApplication].delegate;
[delegate.viewController dismissModalViewControllerAnimated:NO];
if( match.expectedPlayerCount==0 )
{
// Launching the game without waiting for connection change messages
NSLog(#"Begin game without waiting for match connection change messages");
// Determine the host, local or remote
NSArray * playerIds = match.playerIDs;
NSLog(#"Number of players: %d", [playerIds count]);
NSLog(#"ID of player: %#", [playerIds lastObject]);
NSLog(#"I got the player ids");
[GKPlayer loadPlayersForIdentifiers:playerIds withCompletionHandler:^(NSArray *players, NSError * error)
{
//bunch of code that gets player aliases and set host player
//start match
[self schedule: #selector(StartMultiplayerGame) interval:5.];
}
ChangeState code
-(void)match:(GKMatch *)match player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state
{
NSArray * playerIds = [NSArray arrayWithObject:playerID];
switch (state)
{
case GKPlayerStateConnected:
// handle a new player connection.
NSLog(#"Player connected!");
[GKPlayer loadPlayersForIdentifiers:playerIds withCompletionHandler:^(NSArray *players, NSError * error)
{
//bunch of code that gets player aliases and set host player
if (match.expectedPlayerCount==0)
{
//start match
[self schedule: #selector(StartMultiplayerGame) interval:5.];
}
}];
break;
case GKPlayerStateDisconnected:
// a player just disconnected.
NSLog(#"Player disconnected!");
break;
}
-(void)StartMultiplayerGame
{
[[CCDirector sharedDirector] replaceScene:[HelloWorldLayer node]];
}
if (match.expectedPlayerCount==0)
{
//start match
[self schedule: #selector(StartMultiplayerGame) interval:5.];
}
You said it yourself, if minPlayers players have joined (which is 2 in your case) then expectedPlayerCount is 0. So as soon as 2 players have joined, you're starting the game. This is not Game Center's fault.
You could wait for a longer amount of time once expectedPlayerCount is 0 to allow other players to join.
Your code also does not consider that a second player might join, then leave again. So in that case you would be starting the game with just one player.
I am developing an online multiplayer game, but I am struggling with Apple documentation. (I have tried these tutorials by Ray Wenderlichs Part 1 and part 2, but they are not working (match never starts because inviting device never receives the match acceptance).
As this topic is vast, I will be creating a single question, then moving to create another question on SO if necessary.
I want to create an online multiplayer game that will let a user to invite from 1 to 3 people. So, it would be a 2 to 4 people match. The game is not turned based. It is live and the data to be transferred between users is minimum.
Lets start with the basic stuff.
1) the first thing I do is create a notification
if (self.gameCenterAvailable) {
NSNotificationCenter *nc =
[NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(authenticationChanged)
name:GKPlayerAuthenticationDidChangeNotificationName
object:nil];
}
to let me know when the notification changes. When this happens, authenticationChanged method will fire... here it is
- (void)authenticationChanged {
GKMatchmaker sharedMatchmaker].inviteHandler = ^(GKInvite *acceptedInvite, NSArray *playersToInvite) {
// Insert application-specific code here to clean up any games in progress.
if (acceptedInvite)
{
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithInvite:acceptedInvite] autorelease];
mmvc.matchmakerDelegate = self;
[self presentModalViewController:mmvc animated:YES];
}
else if (playersToInvite)
{
GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
request.minPlayers = 2;
request.maxPlayers = 4;
request.playersToInvite = playersToInvite;
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithMatchRequest:request] autorelease];
mmvc.matchmakerDelegate = self;
[self presentModalViewController:mmvc animated:YES];
}
};
}
I grabbed this code from Apple. My question here is this. If Apple say to run this code after the user is authenticated why it is checking for invitation or users to invite? As far as I see, users were not invited yet. Unless the code is not executed at that time, right? It will just sit in memory waiting to be called, WHEN the invitation is done, correct?
If this is the case, I now create an invitation for a match doing
[self dismissModalViewControllerAnimated:NO];
GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
request.minPlayers = minPlayers;
request.maxPlayers = maxPlayers;
request.playersToInvite = self.pendingPlayersToInvite;
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithMatchRequest:request] autorelease];
mmvc.matchmakerDelegate = self;
[self presentModalViewController:mmvc animated:YES];
A window will be present to all users I choose to invite. Suppose the first one taps ACCEPT on the invitation. Which method will be fired on my app, how do I get the user identity and how do I know if all users accepted?
thanks.
First of all please be aware that invitations on the Sandbox enviroment tend to work erratically, so I suggest you start by having all players search for an available match. The code would be something like this:
GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
request.minPlayers = minPlayers;
request.maxPlayers = maxPlayers;
[[GKMatchmaker sharedMatchmaker] findMatchForRequest:request
withCompletionHandler:^(GKMatch *match, NSError *error) {
if (error || !match) {
// handle the error
}
else if (match != nil){
// match found
}}];
}
Then you have to implement the protocol GKMatchDelegate. There's a method there that will be invoked for each player that joins the match and for each player that gets disconnected from it (on this method you can find out the user identity with its playerID):
- (void)match:(GKMatch *)match player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state {
switch (state){
case GKPlayerStateConnected:
if (match.expectedPlayerCount == 0) {
// start the match
}
break;
case GKPlayerStateDisconnected:
// remove the player from the match, notify other players, etc
break;
}
}
Regarding your first question, the code on authenticationChanged is only registering the handlers for those methods, meaning the code that will be invoked when the notifications arrive.
EDIT: Regarding the question on your comment, if the match was started by invitations, the user that started the match has to wait until all invitations are accepted, or cancel some of them and then press Start Match (or something like that) on the Invite Screen. In this scenario the match.expectedPlayerCount == 0 condition will be satisfied once all the players that accepted the invites are connected to the match.
If the match was started by AutoMatch then Game Center does the following: once it finds minPlayers waiting to start a match it will assign them to a match and then wait a few more seconds to see if it can fill the remaining slots. At some point it will start the match with a certain number of players between minPlayers and maxPlayers. Then the condition match.expectedPlayerCount == 0 will be satisfied only once all the players have effectively join the match, but note that when the decision was taken to start a match the number of players expected for that match is already determined by Game Center.
I initiate an online game like this:
request = [[[GKMatchRequest alloc] init] autorelease];
request.minPlayers = 2;
request.maxPlayers = 2;
mmvc = [[[GKMatchmakerViewController alloc] initWithMatchRequest:request] autorelease];
When I receive the delegate method:
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFindMatch:(GKMatch *)match
How to know if the player chose auto-match or invited a friend.
Thanks for advance.
I don't think you can tell at that point, but you could store what happened on your invite handler, something like:
[GKMatchmaker sharedMatchmaker].inviteHandler = ^(GKInvite *acceptedInvite, NSArray *playersToInvite) {
if (acceptedInvite){
// this player has accepted an invite
acceptedInvite = YES;
....
} else if (playersToInvite) {
// this player is making an invite
sentInvite = YES
...
}
};
If none of those variables are set to YES I think you can assume it was an automatch. Remember to set both to NO wherever is appropriate in your game.
I have been trying to implement Game Center multiplayer and struggling to get anything beyond a GKMatchViewController.
I have two views, one is my main menu, this is where multiplayer is launched and the player gets the GKMatchViewController. Then behind this, the user doesn't know it but the view changes to the multiplayer view where they actually play so when the GKMatchViewController is dismissed they are in the game view rather than menu.
Here I launch multiplayer (yes I am using cocos2d):
-(void)mpGo:(id)sender{
GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
request.minPlayers = 2;
request.maxPlayers = 2;
[[GKMatchmaker sharedMatchmaker] findMatchForRequest:request
withCompletionHandler:^(GKMatch *returnedMatch, NSError *error)
{
if (error) NSLog(#"match error: %#", error);
else if (returnedMatch != nil)
{
match = [returnedMatch retain];
match.delegate = self; // start!
}
}];
tempVC = [[UIViewController alloc] init];
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithMatchRequest:request] autorelease];
if (mmvc != nil)
{
mmvc.matchmakerDelegate = self;
[[[CCDirector sharedDirector] openGLView] addSubview:tempVC.view];
[tempVC presentModalViewController:mmvc animated: YES];
}
[[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:1.0 scene:[mpView node] withColor:ccWHITE]];
}
When pressing 'Play Now' I get this error:
match error: Error Domain=GKErrorDomain Code=2 "The requested operation has been cancelled." UserInfo=0x2248d0 {NSLocalizedDescription=The requested operation has been cancelled.}
Then it just stays on that 'Finding Players...' view.
I never done something with GameCenter but maybe I can help anyway. When I get the error
"The requested operation has been
cancelled"
in reverse geocoder I have figured out, that the app had not the time to perform the request.
You get the error by executing findMatchForRequest:, so maybe your request variable is not set completely, check that. You can also try to put a NSLog() in first line (in the block) and look in your passed variable returnedMatch and error