Hi I'm a new bie in Game Center for iOS. I'm trying to add the multiplayer feature using matches to my game and following the documentation.
So far I reached a point where 2 of my clients can successfully get a match, i.e. the matchmakerViewController:didFindMatch callback is called and a GKMatch object is delivered.
However after that I seems to be stuck there forever, because according to the documentation, I'll have to wait until all the players (2 in my case) are actually connected before starting my game. But it seems the match:player:didChangeState callback is never called to indicate a successful connection. Well, I'm sure my clients are all in the same wifi network ( or is it a must?) Could any one enlighten me on this case? Do I have to do any extra things to make the clients to connect? Thanks a lot for the help!
So I was running into this and the solution (for me) was somewhat embarrasing. I had copied and pasted a bunch of the code from the Apple docs..and they left out an obvious step. They never actually set the match's delegate!
My code now is:
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFindMatch:(GKMatch *)match {
[self dismissModalViewControllerAnimated:YES];
self.myMatch = match; // Use a retaining property to retain the match.
self.myMatch.delegate = self; // THIS LINE WAS MISSING IN THE APPLE DOCS. DOH.
// Start the game using the match.
NSLog(#"Match started! Expected Player Count:%d %#",match.expectedPlayerCount, match.playerIDs);}
Once I actually set the match delegate, the functions get called. Doh.
When you get the GKMatch object, be sure to check the expectedPlayerCount property. It is possible that the other player is already connected, and thus you will not get a match:player:didChangeState on the delegate.
I have had the same problem with a friend. The solution was quite strange but it works afterwards. On all devices you have to enable the Notifications (Sounds/Alerts/Badges) for the Game Center inside the Settings/Notifications options. Afterwards we could establish a connection and did receive a match object
Inside your callback matchmakerViewController:didFindMatch
Add this code then you'll see the callback "match:player:didChangeState" being called by GC
GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
request.minPlayers = 2;
request.maxPlayers = 2;
[[GKMatchmaker sharedMatchmaker] addPlayersToMatch:match matchRequest:request completionHandler:^(NSError* error) {
if(error)
NSLog(#"Error adding player: %#", [error localizedDescription]);
}];
It was working all along. The only difference is that... when you use invites the event "didChangeState" doensn't get called. You're connected without notice and you can start to receive data. I never tried to send/receive data... cuz i was expecting the event first, but i did send something by mistake one time, and it worked. :)
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFindMatch:(GKMatch *) match {
//Dismiss window
[self dismissModalViewControllerAnimated:YES];
//Retain match
self.myMatch = match;
//Delegate
myMatch.delegate = self;
//Flag
matchStarted = TRUE;
//Other stuff
}
- (void)match:(GKMatch *)match player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state {
//This code gets called only on auto-match
}
The above code works as expected.
Make sure that you've set your class as the delegate for GKSession. The class will need to implement the GKSessionDelegate protocol... otherwise, it'll never receive this callback. Here's the protocol reference. Hope this helps!
Related
This problem has stumped me for two nights now, I cannot simply have two LHSprites collide with each other using level helper collision. I have checked the docs multiple times to check if I was missing something, but i cannot see it.
In the console of xCode, it logs:
LevelHelper WARNING: Please call useLevelHelperCollisionHandling after addObjectsToWorld
2013-04-24 20:33:04.537 Monkeys2D[1322:c07]
LevelHelper WARNING: Please call registerPostCollisionCallbackBetweenTagA after useLevelHelperCollisionHandling
But as you can see in my code below, I am correctly doing what it is warning me that I am not.
-(id) init
{
if( (self=[super init])) {
self.isTouchEnabled = YES;
loader = [[LevelHelperLoader alloc]initWithContentOfFile:#"Level1"];
[loader addObjectsToWorld:world cocos2dLayer:self];
[loader useLevelHelperCollisionHandling];
[loader registerPostCollisionCallbackBetweenTagA:MONKEY andTagB:SINGLEBANANA idListener:self selListener:#selector(collision)];
}
return self;
}
My problem is that the collision will NOT register, it acts as if nothing is even happening when the MONKEY and the SINGLEBANANA collide.
Any help would be greatly appreciated!
Turns out i needed to add an update method and implement some other box2d stuff, visit the level helper docs & forum to see.
I am trying to geocode address into coordinates using following code:
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:#"6138 Bollinger Road, San Jose, United States" completionHandler:^(NSArray* placemarks, NSError* error){
for (CLPlacemark* aPlacemark in placemarks)
{
// Process the placemark.
NSString *latDest1 = [NSString stringWithFormat:#"%.4f",aPlacemark.location.coordinate.latitude];
NSString *lngDest1 = [NSString stringWithFormat:#"%.4f",aPlacemark.location.coordinate.longitude];
lblDestinationLat.text = latDest1;
lblDestinationLng.text = lngDest1;
}
}];
I have tried it many times but the debugger never enters the block and I am not able to get the location. What can I try next?
All right I found my mistake.
The code is correct and works perfect. All the while I was working on it was through debugger and was trying to figure out why the debugger did not enter the block. But now I have found out debugger does not enter the block at that moment. It takes little in getting the location values. It is done asynchronously so I was not able to find it and I was getting crash because of no values just after the crash. I have moved my code post block to inside the block and everything works fine for me now.
I just ran that exact code and it worked as expected. Make sure that you have an active internet connection.
Try adding a NSLog on the strings and see if it gets called.
NSLog(#"lat: %#, lng: %#", latDest1, lngDest1);
Are you running it in the simulator or the device?
Blocks are new features to Objective C from iOS4.0 onwards. A block you can imagine as a delegate method working in same functional block. As for any delegate method it takes time to invoke, depending upon the condition, same way block executes the code inside it, when it completes its work of geocoding. You can read more about Block in apples documentation or read http://www.raywenderlich.com/9438/how-to-use-blocks-in-ios-5-tutorial-part-2.
You can also have look into my repository on GITHUB https://github.com/Mangesh20/geocoding
I'm trying to set up a socket in VoIP mode on an iPhone, so that my app can be woken when an event happens. I have a simple server that will write to the socket if and only if the app should wake up and talk to the main web service about something. Calling
CFReadStreamSetProperty()
on the stream attached to the socket always seems to return zero, which if I'm not mistaken is FALSE, meaning the stream did not recognize and/or accept the property value. I read in a previous question that this facility is not available on the simulator, so I tried it on a real phone, with the same result.
How can I figure out why the call is failing?
The code is below:
- (id) init {
NSLog(#"NotificationClient init, host = %#", [self getNotificationHostName]);
CFHostRef notificationHost = CFHostCreateWithName(kCFAllocatorDefault, (CFStringRef)[self getNotificationHostName]);
CFStreamCreatePairWithSocketToCFHost(kCFAllocatorDefault, notificationHost, [self getNotificationPort], &_fromServer, &_toServer);
BOOL status;
status = CFReadStreamOpen(_fromServer);
status = CFReadStreamSetProperty(_fromServer, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
NSLog(#"status from setting VoIP mode on socket is %d", status);
status = CFWriteStreamOpen(_toServer);
[self sendMessage:#"STRT" withArgument:#"iPhone"];
[self startReceivingMessages];
return self;
}
Hmm... it looks like there were two problems. First, you need to set the property before opening the stream. And second, it looks like it only works if you are on the main thread when you do this.
I just can't figure out how this works. What I am trying to do is let two players play a game if a third player joins it can instantly join the game, if the fourth and last player joins it can also instantly join the game. They can also leave the game at anytime for whatever reason, if that happens there should be a space open for another person or for the same person to reconnect. That's the idea.
Now what I got is the following. I authenticate the local player for obvious reasons. Then I search for a match like so:
if (matchRequest) [matchRequest release];
matchRequest = [[GKMatchRequest alloc] init];
matchRequest.minPlayers = 2;
matchRequest.maxPlayers = 4;
[[GKMatchmaker sharedMatchmaker] findMatchForRequest:matchRequest withCompletionHandler:^(GKMatch *match, NSError *error) {
if (error) {
// An error occured
} else {
if (matchCurrent) [matchCurrent release];
matchCurrent = [match retain];
matchCurrent.delegate = self;
}
}];
If I execute this part on three different devices, two of them will find each other and the third is still looking. So I figured after the find match for request has found the minimum amount of players it will be executed once. So what I needed was a method that used the matchCurrent that I retained to add more players. Luckely that method existed, but how would that work? When do you call it in this case? I decided to put it under a button so I could manually execute it when a match has been found.
What I discovered is that when I pressed it on the first device, finally the third device could find the match the first and second device were in. In fact the second and third device contained the playerIDs of every device involved. Which is a good thing. But there are two problems.
Which device should actually call the addPlayersToMatch method? And how can you restrict it to one device executing that method? Plus when should you call it?
Why, on the device calling that method, isn't the playerIDs updated?
[[GKMatchmaker sharedMatchmaker] addPlayersToMatch:matchCurrent matchRequest:matchRequest completionHandler:^(NSError *error) {
//matchCurrent.playerIDs is not updated?!
}];
Actually they are updated. When I see the playerIDs appear on the second and third device I manually update the matchCurrent.playerIDs on device one and suddenly it does recognize the player. However even the 'didChangeState' for player is not called when the new player is discovered on device one.
Your using the Apple iOS Game Center GKMatchmaker class. I'm assuming you are using a peer to peer connection, not hosted.
The GKMatch class gives the playerIDs array to you.
#property(nonatomic, readonly) NSArray *playerIDs
This is an ordered list, so you might be able to use it to select the first player call addPlayersToMatch.
Linked below is some documentation.
http://developer.apple.com/library/ios/#documentation/GameKit/Reference/GKMatchmaker_Ref/Reference/Reference.html
http://developer.apple.com/library/ios/#documentation/GameKit/Reference/GKMatch_Ref/Reference/Reference.html
http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/GameKit_Guide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008304
Which device should actually call the
addPlayersToMatch method? And how can
you restrict it to one device
executing that method?
You could solve this problem by having the devices "draw straws". Each device generates a random number, then sends it to the others. The device with the largest number is the leader, and it is the one that must call addPlayersToMatch. If two devices pick the same number, throw out the numbers and start over.
I would suggest that you periodically (maybe once per second or two) poll for the status of other players, so you can detect if anyone has joined, or left for any reason. Unless the iphone architecture you are using already provides an function that handles that event.
It sounds like you may want to find some more documentation and/or sample code for the multi-player framework you are using.
I'm doing this with Unity via Prime31's GameCenterMultiplayerBinding. I'm not entirely sure how it maps to GameKit (the docs are sparse and don't give those details), but the names are very suggestive.
To be able to match 2 to 4 players into a match, I'm doing:
findMatchProgrammaticallyWithFilters(
minPlayers: 2,
maxPlayers: 4,
playerGroup: GetCurrentPlayerGroup(),
playerAttributes: 0);
I assume this maps to findMatchForRequest:withCompletionHandler.
After that succeeds, I call:
finishMatchmakingForMatch();
Which surely maps to finishMatchmakingForMatch. I found that calling findMatchForRequest or addPlayersToMatch would fail with "The requested operation has been canceled or disabled by the user" if I didn't call finishMatchmakingForMatch first.
addPlayersToCurrentMatchWithUpdatedMatchRequest(
minPlayers: 2,
maxPlayers: 4,
playerGroup: GetCurrentPlayerGroup(),
playerAttributes: 0,
playersToInvite: null);
addPlayersToMatch:matchRequest:completionHandler
After that succeeds, I call:
finishMatchmakingForMatch();
If I have space for more players, I loop and call addPlayersToMatch again.
I'd expect that since I need to call finishMatchmakingForMatch when I'm done
matchmaking, firing findMatchProgrammatically would keep looking until it
found maxPlayers, but it doesn't. It gives up after the first match. So we need
to call addPlayersToMatch.
I am trying to create class that will handle multiple downloads at same time (I need to download a lot of small files) and I have problems with "disappearing" connections.
I have function addDonwload that adds url to list of urls to download, and checks if there is free download slot available. If there is one it starts download immediately. When one of downloads finishes, I pick first url form list and start new download.
I use NSURLConnection for downloading, here is some code
- (bool) TryDownload:(downloadInfo*)info
{
int index;
#synchronized(_asyncConnection)
{
index = [_asyncConnection indexOfObject:nullObject];
if(index != NSNotFound)
{
NSLog(#"downloading %# at index %i", info.url, index);
activeInfo[index] = info;
NSURLRequest *request = [NSURLRequest requestWithURL:info.url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15];
[_asyncConnection replaceObjectAtIndex:index withObject:[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:TRUE]];
//[[_asyncConnection objectAtIndex:i] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
return true;
}
}
return false;
}
- (void)connectionDidFinishLoading:(NSURLConnection*)connection
{
[self performSelectorOnMainThread:#selector(DownloadFinished:) withObject:connection waitUntilDone:false];
}
- (void)DownloadFinished:(id)connection
{
NSInteger index = NSNotFound;
#synchronized(_asyncConnection)
{
index = [_asyncConnection indexOfObject:(NSURLConnection*)connection];
}
[(id)activeInfo[index].delegate performSelectorInBackground:#selector(backgroundDownloadSucceededWithData:) withObject:_data[index]];
[_data[index] release];
[activeInfo[index].delegate release];
#synchronized(_asyncConnection)
{
[[_asyncConnection objectAtIndex:index] release];
[_asyncConnection replaceObjectAtIndex:index withObject:nullObject];
}
#synchronized(downloadQueue)
{
[downloadQueue removeObject:activeInfo[index]];
[self NextDownload];
}
}
- (void)NextDownload
{
NSLog(#"files remaining: %i", downloadQueue.count);
if(downloadQueue.count > 0)
{
if([self TryDownload:[downloadQueue objectAtIndex:0]])
{
[downloadQueue removeObjectAtIndex:0];
}
}
}
_asyncConnection is my array of download slots (NSURLConnections)
downloadQueue is list of urls to download
What happens is, at the beginning everything works ok, but after few downloads my connections start to disappear. Download starts but connection:didReceiveResponse: never gets called. There is one thing in output console that I don't understand I that might help a bit. Normaly there is something like
2010-01-24 21:44:17.504 appName[3057:207]
before my NSLog messages. I guess that number in square brackets is some kind of app:thread id? everything works ok while there is same number, but after some time, "NSLog(#"downloading %# at index %i", info.url, index);" messages starts having different that second number. And when that happens, I stop receiving any callbacks for that urlconnection.
This has been driving me nuts as I have strict deadlines and I can't find problem. I don't have many experiences with iphone dev and multithreaded apps. I have been trying different approaches so my code is kinda messy, but I hope you will see what I am trying to do here :)
btw is anyone of you know about existing class/lib I could use that would be helpful as well. I want parallel downloads with ability o dynamically add new files to download (so initializing downloader at the beginning with all urls is not helpful for me)
You've got a bunch of serious memory issues, and thread synchronization issues in this code.
Rather than go into them all, I'll ask the following question: You are doing this on a background thread of some kind? Why? IIRC NSURLConnection already does it's downloads on a background thread and calls your delegate on the thread that the NSURLConnection was created upon (e.g., your main thread ideally).
Suggest you step back, re-read NSURLConnection documentation and then remove your background threading code and all the complexity you've injected into this unnecessarily.
Further Suggestion: Instead of trying to maintain parallel positioning in two arrays (and some sketchy code in the above relating to that), make one array and have an object that contains both the NSURLConnection AND the object representing the result. Then you can just release the connection instance var when the connection is done. And the parent object (and thus the data) when you are done with the data.
I recommend that you take a look at this:
http://allseeing-i.com/ASIHTTPRequest/
It's a pretty sophisticated set of classes with liberal licensing terms (free too).
It may provide a lot of the functionality that you are wanting.
This snippet can be the source of the bug, you release the object pointed to by the activeInfo[index].delegate pointer right after issuing async method call on that object.
[(id)activeInfo[index].delegate performSelectorInBackground:#selector(backgroundDownloadSucceededWithData:) withObject:_data[index]];
[_data[index] release];
[activeInfo[index].delegate release];
Do you use connection:didFailWithError: ? There may be a timeout that prevents the successful download completion.
Try to get rid of the #synchronized blocks and see what happens.
The string inside the square brackets seems to be thread identifier as you guessed. So maybe you get locked in the #synchronized. Actually, I don't see a reason for switching thread - all the problematic code should run in the main thread (performSelectorOnMainThread)...
Anyhow, there is no need to use both the #synchronized and the performSelectorOnMainThread.
BTW, I didn't see the NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; line. Where do you initiate the connection?
As for the parallel downloads - I think that you can download more than one file in a time with the same code that you use here. Just create a separate connection for each download.
Consider just keeping a download queue along with a count of active connections, popping items off the top of the queue when downloads complete and a slot becomes free. You can then fire off NSURLConnection objects asynchronously and process events on the main thread.
If you find that your parallel approach prohibits doing all of the processing on the main thread, consider having intermediary manager objects between your main thread download code and NSURLConnection. Using that approach, you'd instantiate your manager and get it to use NSURLConnection synchronously on a background thread. That manager then completely deals with the downloading and passes the result back to its main thread delegate using a performSelectorOnMainThread:withObject: call. Each download is then just a case of creating a new manager object when you've a slot free and setting it going.