The next step is to register your app with the iTunes Store. If you thought you just did that, you didn’t.
What you’ve done is register an ID. Now you need to assign that ID to an app.
Log into your iOS Dev Center account (http://developer.apple.com/devcenter/ios) page and locate the iTunes Connect link. iTunes Connect is the portal you use to work with the iTunes Store. It’s also the place where you setupand configure other online support services, such as Game Center, In-App Purchases, advertising, and subscription apps (magazines).
After signing into iTunes Connect, locate and click on the Manage Your Apps link. This will list all of the apps you have registered with the App Store. Apps go through a process of registration, configuration, submission, andapproval before they appear in the App Store. To test an app with Game Center, you must register and configure your app. Click on the Add New App button and fill in the details, as shown in Figure 14-10.
Figure 14-10. Adding a new app to the App Store
Select the default language and give your app a name (“Sun Touch”). This is the name of your app, as it will appear in the store. You must assign your app an SKU number, although it can be any identifier you choose. It only has to be unique among the apps that you develop, and Apple doesn’t use it for anything except reporting.
Finally, choose the bundle ID you created in the previous section from the pop-up menu. If you forgot to create a unique ID, there’s a convenient link that will take you back to the Certificates, Identifiers & Profiles page where you can correct that oversight.
You’ll then be led through a series of screens that let you set the date your app will be available for sale, its price, its category, any content warnings, the required App Store icons andscreen shots, a description of your app, and so on.
Unless you actually plan to publish a version of SunTouch on the App Store, you don’t need to be too concerned with the answers. Files for the required uploads (icons and screen shots)can be found in the Learn iOS Development Projects ➤ Ch 14 ➤ SunTouch (iTunes Connect) folder.
When you’re finished, your app will appear in iTunes Connect. Locate your new app and click on its icon to manage it, as shown in Figure 14-11.
Figure 14-11. Managing an app in iTunes Connect
Configuring Game Center
Once you’ve created your app in iTunes Connect, click on the Manage Game Center button
(see Figure 14-11).
The Game Center management page is where you enable Game Center features for your app. Your app is already enabled for use with Game Center, as shown in Figure 14-12. If it wasn’t, just turn it on now.
Figure 14-12. Configuring Game Center
To use the leaderboards feature in your app, you must create one or more leaderboards in the Game Center. You can create single scoreboards that are independent of one another, or combined scoreboards that aggregate otherscoreboards. For SunTouch, create two independent (single) scoreboards by clicking the Add Leaderboard button, shown in Figure 14-12. Configure the boards as follows:
1. Choose to create a Single leaderboard
2. Fill in the leaderboard reference name and leaderboard ID (see table)
3. Score format type: Integer
4. Score Submission Type: Best Score
5. Sort Order: High to Low
6. Leave Score Range empty
7. Add at least one language to the Leaderboard Localization list
a. Give the leaderboard a name in the choosen language (see table)
b. Select the appropriate score formatting options (US English uses comma separators in integer numbers, for example)
Reference Name
|
Leaderboard ID
|
Name
|
SingleMulti
|
singlemultiple
|
Single PlayerMulti-Player
|
Repeat these steps to create the second leaderboard. Your finished leaderboards should look like those in Figure 14-13. The leaderboard ID is the key you’ll use in your app to refer to the leaderboard when submitting scores. Thereference name is for your use. You’ll use the “Single” leaderboard to record one-person game scores, and the “Multi” leaderboard to record two-player game scores.
Figure 14-13. Finished leaderboards
Save your work by clicking the Save button and sign out of iTunes Connect. Your work there is done.
Now you can add GameKit support to your app and create a test user.
Adding GameKit to Your App
The Game Center–aware version of SunTouch can be found in the Learn iOS Development Projects
➤ Ch 14 ➤ SunTouch-3 folder. You can open that, or follow these steps to add Game Center support to the single-player version of the game.
Now you need to add the code to your app to activate and interact with Game Center. At a minimum, you must:
n Obtain the local player as soon as possible
n If the local player is not logged in, present the login view controller
n Disable your game if the local player cannot, or refuses to, log in
n Add a button to your interface to allow the user to interact with Game Center
For games that record scores to a leaderboard, you must:
n Report each score to the appropriate leaderboard
Let’s get started.
Obtaining the Local Player
The very first thing your app should do after launching is to obtain the local player (GKLocalPlayer) object. Add this code to STMainViewController.m:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
weak GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer]; localPlayer.authenticateHandler =
^(UIViewController *viewController, NSError *error) { if (viewController!= nil)
[self showAuthenticationView:viewController]; else if (localPlayer.authenticated)
[self authenticatePlayer:localPlayer]; else
[self disableGameCenter];
};
}
The local player object represents the user’s identity in Game Center. Normally, a user will already be logged into Game Center and there’s nothing to do. If they have not logged in, you should immediately present the GameCenter authentication view, which allows the user to log into their Game Center account. If they can’t, or won’t, you should disable your game or run it in a mode that doesn’t interact with Game Center.
All of those cases are handled in the code block you set for the authenticateHandler property. Unlike most objects, the process of logging into Game Center isn’t one you explicitly initiate. It’s an ongoing endeavor, as the playercan log out or switch accounts at any time (using the Game Center app on their iOS device). The code block you set in authenticateHandler is executed whenever there’s a change in the player’s Game Center status.
In SunTouch, there are three conditions that require action. If the viewController is not nil, Game Center is telling your app that it wants it to present that view controller to the user. Typically, this is because the player is notlogged in; that view controller presents a login screen so the player can authenticate with Game Center.
The second condition is that the player is now, or was already, authenticated and is ready to play your game. At this point your game should prepare itself for play. In SunTouch, the -authenticatePlayer: method readies itself bydisplaying the two “start game” buttons.
Finally, Game Center will signal your app that the local player is not, or is no longer, authorized. In other words, they logged out or weren’t logged in. SunTouch sends a -disableGameCenter message that hides the two “startgame” buttons. The user won’t be able to play the game until they log in.
To finish this code, you need to add those three methods to the STMainViewController.m file:
- (void)showAuthenticationView:(UIViewController*)viewController
{
[self presentViewController:viewController animated:YES completion:NULL];
}
- (void)authenticatePlayer:(GKLocalPlayer*)player
{
self.singlePlayButton.hidden = NO; self.multiPlayButton.hidden = NO;
}
- (void)disableGameCenter
{
self.singlePlayButton.hidden = YES; self.multiPlayButton.hidden = YES;
}
Adding a Game Center Button
You’ve already handled the first three requirements (obtaining the local player, logging the player in, and disabling the game when they aren’t). All Game Center–aware apps should also provide a button so the user can access theGame Center interface from within the app. This is where the user can
see their leaderboards, scores, and achievements.
In SunTouch, add a small (22x22 pixel) custom button to the main view controller in the Main_iPhone. storyboard (or _iPad) file. Use the GameCenter.png image, as shown in Figure 14-14.
Fix its height and width, and addconstraints to the Bottom Layout Guide and the closest container view edge.
Figure 14-14. Adding a Game Center button
Switch to the assistant editor, so that STMainViewController.h appears in the right pane. Add an action method declaration:
- (IBAction)showGameCenter;
Connect the game center button to this action (see Figure 14-14).
While you’re in STMainViewController.h, there are some loose ends that need to be taken care of. The earlier code uses two button outlets. Declare those and connect themto the Single Player and Two Player buttons:
@property (weak,nonatomic) IBOutlet UIButton *singlePlayButton;
@property (weak,nonatomic) IBOutlet UIButton *multiPlayButton;
Select the two buttons in the storyboard and use the attributes inspector to hide them (by checking the hidden attribute). This way, the buttons won’t appear when the app starts. If the player is logged into Game Center, the -authenticatePlayer: method will immediately reveal (unhide) them. The player probably won’t even notice. This precaution does, however, prevent a nimble-fingered user from starting a game before the status of the local player canbe determined.
If you’re building both versions of the app, make the same changes (add the Game Center button, connect it to the -showGameCenter action, connect the two outlets to the buttons, and hide the buttons) in the other storyboard(_iPhone or _iPad).
For reasons I’ll explain in a moment, your STMainViewController will need to adopt the
GKGameCenterControllerDelegate, so add that now (new code in bold):
@interface STMainViewController : UIViewController
<STFlipsideViewControllerDelegate, UIPopoverControllerDelegate, GKGameCenterControllerDelegate>
Finally (or firstly) import the GameKit.h header:
#import <GameKit/GameKit.h>
Now switch to STMainViewController.m and add the -showGameCenter action method and the
GKGameCenterControllerDelegate handler method:
- (IBAction)showGameCenter
{
GKGameCenterViewController *gameCenterController; gameCenterController = [GKGameCenterViewController new];
if (gameCenterController!=nil)
{
gameCenterController.gameCenterDelegate = self; [self presentViewController:gameCenterController
animated:YES completion:nil];
}
}
- (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController*)controller
{
[self dismissViewControllerAnimated:YES completion:nil];
}
You’ve seen this kind of code before. The -showGameCenter method creates a GKGameCenterViewController, sets itself as the delegate, and modally presents the view controller to the user. When the view controller is done, your delegatereceives a -gameCenterViewControllerDidFinish: message (which is why your controller had to adopt GKGameCenterControllerDelegate) that dismisses the game center view controller.
You’ve now satisfied the minimum requirements for a Game Center–aware app. But SunTouch uses leaderboards, so in the next section you’ll add code to record the player’s score.
Recording Leaderboard Scores
Recording the player’s score to the leaderboard is probably the simplest part of your app. You only need two pieces of information: the leaderboard identifier and the final score. Select the STGameViewController.m file and locatethe -finishGame method. At the end of the if block, change the code so it looks like this (new code in bold):
self.strikePreview.hidden = YES;
GKScore *scoreReport;
scoreReport = [[GKScore alloc] initWithLeaderboardIdentifier:
kSinglePlayerLeaderboardID];
scoreReport.value = score;
[GKScore reportScores:@[scoreReport] withCompletionHandler:^(NSError *error) {
}];
}
The initWithLeaderboardIdentifier: initializer method creates a GKScore object for the leaderboard you want to post the score to.
You then set the score property you want to report and send it to the Game Center servers
(+reportScores:withCompletionHandler:).
The completion handler block is executed when the score has been, or fails to be, delivered.
You can examine the error parameter to see if it was successful and take whatever action you feel is appropriate. SunTouch isn’t overly concerned if the player’s score couldn’t be posted.
Leaderboards are addressed using their ID. Switch to the STGameDefs.h file and add these two constants:
#define kSinglePlayerLeaderboardID @"single"
#define kTwoPlayerLeaderboardID @"multiple"
Now the -initWithLeaderboardIdentifier: method will create a leaderboard for the “single” leaderboard you defined in iTunes Connect.
Creating a Test Player
The only thing left to do is test your app. To do that, you must have a player account in Game Center. But it can’t be just any player; it must be a sandbox player. Until your app is submitted and approved for distribution on theApp Store, your app will use the Game Center sandbox. The Game Center sandbox is a set of servers that work identically to the way the public Game Center servers do, but
the information, players, and scores are all private and are only used for development and testing.
When you run your pre-approved app, Apple automatically places it in the sandbox. You create a sandbox player by creating a new player account from within your (sandboxed) app. This can be done via iOS simulator or aprovisioned device. The steps are simple:
1. If you already have a Game Center player account, launch the Settings app, go to the Game Center settings, and log out (tap your account name, choose Sign Out). See Figure 14-15.
Figure 14-15. Creating a sandbox player
2. Launch your app.
3. Since no player is logged in, your app will immediately present the player sign-in view controller (see Figure 14-15).
4. Create a new player account.
Creating an account in a sandboxed app creates a sandbox player account. You must provide an
e-m ail address for your account that is not associated with any regular Apple ID, so you may need to create a new e-mail account for testing.
Play a few games. You’ll see your scores appear on the leaderboard, which you can access via the Game Center button you added to your app, as shown in Figure 14-16.
Figure 14-16. Playing your Game Center-aware app
Congratulations! You’ve created a Game Center–aware app, configured the Game Center servers, created a sandbox player account, and posted scores to your leaderboard. Which finally brings you to the point where you canadd networking to your app.
Peer-To-Peer Networking
The moment you’ve been waiting for has arrived: adding peer-to-peer networking to your app.
Broadly speaking, this will necessitate making three changes to SunTouch: Turning the single-player game into a two-player game Discovering and connecting with another iOS device
Sending and receiving game data
Start with the first. In the two-player game, two players (one local and one remote) will be simultaneously blasting holes in space, trying to capture suns before the other player can, in a battle royale to become master of theuniverse—or something like that. The interface needs change to:
Show the strike animation of the opposing player
Show the areas of space blasted by the opposing player Animate and display suns captured by the opposing player
You already have code to animate strikes and show the areas of space that have already been blasted. You also have code that animates a sun being captured. Can you reuse this code to do the same for the opposing player? Ithink you can.
Turning SunTouch Into a Two-Player Game
You’re going to make a minor change to STGameView so it “draws” transparent circles in the
blasted holes, instead of filling them with black. You’ll then create a subclass of STGameView, called STOpponentGameView, and position an instance of that view directly behind the STGameView object in the interface. The opponent game view will animate strikes and draw the blasted holes for the opponent. These animations and struck areas will only be visible through the transparent holes drawn in the foreground (local) game view. The effect will be like looking at a slice of Swiss cheese through a second slice of Swiss cheese.
For the sun capture animations, you want the local player to see the suns being captured by their opponent. This adds to the strategy of the game; by observing where the opponent is capturing suns, the local player can inferwhat areas of space their opponent has already blasted. To be visible in the interface, the sun capture animation must occur in a view above the local player view. This
is solved by having the local game view perform all sun capture animations. The only thing that changes is the color of the suns, indicating which player captured them.
That’s the bulk of the two-player changes. Beyond that, there’s a bunch of small details to attend to, which I’ll get to shortly. Start by making those small changes in STGameView. Select the STGameView.h file and add anopponent property:
@property (readonly,nonatomic) BOOL opponent;
Now switch to the STGameView.m implementation file and add the getter method for this property:
- (BOOL)opponent
{
return NO;
}
The opponent property will indicate if the view is displaying the game for the local or remote player. STGameView always returns NO, because it only displays the view for the local player. STOpponentGameView will returnYES, because it always displays the view for the remote player.
The game view displays and animates the strikes initiated by the player, now players. It does this by
observing kGameStrikeNotification notifications.
In the two player game, there are now two sources for these notifications:strikes by the local player and strikes fromthe remote player. The code that sends these notifications will change to indicate the source of the strike. Change the
-strikeNotification: method so it starts like this (new code in bold):
- (void)strikeNotification:(NSNotification*)notification
{
NSDictionary *info = notification.userInfo; STStrike *strike = info[kGameInfoStrike];
BOOL opponent = [info[kGameInfoOpponent] boolValue]; if (opponent!=self.opponent)
return;
The new code gets the kGameInfoOpponent value from the notification. This value will be YES if
the strike notification is coming from the remote player. It compares that value against this view’s opponent property. If the values disagree, the notification is ignored. The end result? The local game view only animates strikesfrom the local player and the opponent game view only animates strikes from the remote player.
This leaves the sun capture animation to fix. The sun capture animations for both players
are handled by the local (foreground) view. The only thing that changes is the image used for the suns. A SunCold.png image indicates a sun that was captured by the opponent. Find the
-captureNotification: method and change the last statement to this (modified code in bold):
sunView.image = [UIImage imageNamed:(sun.localPlayer?@"SunHot":@"SunCold")];
This alteration uses a different sun image if the sun was captured by the opponent. (You haven’t created the localPlayer property for STSun yet, but you’ll get to that soon enough.)
Finally, modify the -setStrikeDrawColor method to this (new code in bold):
- (void)setStrikeDrawColor
{
if (self.opaque)
{
[[UIColor blackColor] set];
} else
{
[[UIColor clearColor] set]; CGContextSetBlendMode(UIGraphicsGetCurrentContext(),kCGBlendModeCopy);
}
}
The -setStrikeDrawColor method does exactly what it says. It’s sent by the -drawRect: method when it wants to set the graphics context color used to draw a hole in space. In the single-player
game, the local game view is opaque and strikes draw as black circles. In the two-player game, the local game view can be partially transparent (!opaque), and the holes are really holes; the context
is set to “draw” with invisible pixels, making whatever portion of the view being filled transparent.
Normally, the blend mode does not draw transparent pixels, which is why the blend mode is changed to kCGBlendModeCopy. The kCGBlendModeCopy mode performs no blending at all, replacing pixels in the context with thecurrent color.
Subclassing STGameView
The remaining differences between the local and opponent’s game view are supplied by the STOpponentGameView class. Create that class now. Select the STGameView.m file in the navigator and choose the New File . .. command. Use the Objective-C file template. Name the new class STOpponentGameView and make it a subclass of STGameView.
STOpponentGameView doesn’t define any new properties or methods. It does all of its magic by overriding methods defined in STGameView. Here is all of the code to make STOpponentGameView work.
@implementation STOpponentGameView
- (void)observeNotificationsFromGame:(STGame*)game
{
[super observeNotificationsFromGame:game]; if (game!=nil)
[[NSNotificationCenter defaultCenter] removeObserver:self name:kGameSunCaptureNotification
object:game];
}
- (BOOL)opponent
{
return YES;
}
- (UIImage*)strikeImage
{
return [UIImage imageNamed:kOpponentStrikeImageName];
}
- (void)setStrikeDrawColor
{
[[UIColor blackColor] set];
}
- (void)drawBackground
{
[[UIColor darkGrayColor] set]; CGContextFillRect(UIGraphicsGetCurrentContext(),self.bounds);
}
@end
The -observeNotificationsFromGame: method is sent when the game begins. The game view becomes the observer of key game engine notifications. This consists of observing the “strike” and “sun captured” notifications, so theview can draw and animate those events. In the case of a two-player game, however, all of the “sun captured” events are animated by the local (foreground) view. None of them are animated by the opponent (background) view. Rather than adding code to
-captureNotification: to ignore them (the way you did in -strikeNotification:), the opponent view simply un-registers itself again from the notification center. It will still receive the “strike” notifications, but won’t receive any “suncaptured” notifications. The remaining methods override methods in STGameView. When the -drawRect: method in STGameView sends itself the -setStrikeDrawColor and -drawBackground messages, the opponent game view will execute this codeinstead.
The finished STOpponentGameView class draws its strike animation using the kOpponentStrikeImageName image, draws its strike holes in black, and fills the rest of its view with dark grey. It draws and animates only strikeevents from the remote player, and it doesn’t animate any sun capture events.
Now you just need to add an STOpponentGameView object to your interface.
Adding the Opponent Game View
Select the STGameViewController.xib Interface Builder file. From the object library, drag a new View object into the outline. Carefully insert the new view so it’s a subview of the root view, and is ordered before (behind) the existinggame view object. Do this by dropping the new view into the outline, as shown in Figure 14-17. You can’t drop the new view object into the canvas, as you normally would, because it would become a subview of some other view, which is not what you want.
Figure 14-17. Inserting the opponent game view
Resize the view, using the resize handles or the size inspector, so it has the same frame as the game view. Add the exact same set of constraints you did for the game view; align the top, leading, and trailing edge to thesuperview, and add a vertical spacing constraint from the bottom to the top of
the strike preview view. Using the identity inspector, change its class to STOpponentGameView, as shown in Figure 14-18.
Figure 14-18. The finished opponent game view
Switch to the assistant editor. The STGameViewController.h file should appear in the right pane. Use the navigation ribbon above the pane to switch to the STGameViewController.m implementation file, as shown in Figure 14-19.
The Interface Builder outlets for the game views are private outlets, used only by STGameViewController.
Figure 14-19. Adding an opponentGameView outlet
Define a second STGameView outlet named opponentGameView (see Figure 14-19) and connect it to
the new game view object, also shown in Figure 14-19. Make the connection by dragging the outline connection socket to the Opponent Game View object in the outline. (Since this view is behind all of the other views, it’sdifficult to make the connection in the Interface Builder canvas.)
Odds and Ends
There are a smattering of additional code changes, some obvious and some not so obvious, that will finish turning your app into a two-player game. I’ll summarize the changes here. If you’re modifying the single-playerversion of SunTouch as you work through this section, use this as a guide to locate and copy the updated code from the source files in the Learn iOS Development Projects ➤ Ch 14 ➤ SunTouch-4 folder.
n STSun.h
n Add a Boolean localPlayer property. This property establishes which player captured the sun.
n STGame.h
n Add a readonly opponentScore property that calculates the score of the opposing player.
n The -willCaptureSunAtIndex:gameTime: method is renamed to -willCaptu reSunAtIndex:gameTime:localPlayer:. The new parameter is YES when the sun is being captured by the local player.
n Define a kGameInfoOpponent key, used to identify the source of strike notifications.
n STGame.m
n Add a synthetic, readonly, Boolean property named twoPlayer. This property is YES if this is a two-player game.
n In -weightAtTime:, double the value of the weight if twoPlayer is YES. Scores for capturing a sun in a two-player game are doubled.
n Implement the -opponentScore getter method. The -score and
-opponentScore methods both use the new -scoreForLocalPlayer: method, which calculates the score for either player.
n Add a new -startMultiPlayerWithMatch:started: method. This method is sent, instead of -startSinglePlayer, to start a two-player game.
n Change -strike:radius:inView: so it passes YES for the localPlayer
parameter when sending -willCaptureSunAtIndex:gameTime:localPlayer:.
n -willCaptureSunAtIndex:gameTime:localPlayer: sets the localPlayer property of the sun that will be captured. This determines the image for the captured sun and which player gets credit.
n STGameViewController.h
n Add a Boolean twoPlayer property. This property is set to YES when the user starts a two-player game.
n STGameViewController.m
n In -viewDidLoad, twoPlayer is used to configure the views for a one- or
two-player game. For a one-player game, the opponentGameView is removed (deleted), since it’s not used, and the local game view’s opaque property is set to YES. For a two-player game, both game views are used and the local game view’s opaque property is set to NO, allowing it to have transparent regions that will show the opponent game view behind it.
n In -finishGame, the twoPlayer property selects the end-of-game alert message. The two-player alert tells the local player if they won (or lost), and what both scores were. Two-player game scores areposted to the kTwoPlayerLeaderboardID leaderboard.
n STMainViewController.m
n In the -prepareForSegue:sender: method, the game view controller’s twoPlayer property is set to YES or NO before it is presented, based on the segue’s identifier (“singlePlayer” or “twoPlayer”). Setting thisproperty to YES begins the cascade of events that creates and runs a two-player game.
n Main_iPhone.storyboard/Main_iPad.storyboard
n Create a modal segue from the “two player” button to the game view controller. Set the segue’s identifier property to twoPlayer.
This completes the front-end of your two-player game. Now comes the network communications portion that will connect the game with another user playing on a second iOS device.
Matchmaking
Real-time, peer-to-peer, game communications can be roughly divided into two phases: matchmaking and live communications. Matchmaking is, by far, the most complicated, which is why it’s so great that GameKit is going todo it for you.
Matchmaking is the process of discovering and connecting with a second instance of your app running on another iOS device. The biggest impact it will have on your app’s design is that it radically changes how the game starts. In the single player version, STGameViewController created an STGame object and sends it a -startSinglePlayer message. This immediately starts the game.
Starting the two-player version is a multi-step process:
1. STGameViewController creates a GKMatchRequest object.
2. The match request is used to create and present a
GKMatchmakerViewController.
3. The app waits for the CKMatchmakerViewController to locate and connect with a second player.
4. If successful, the -matchmakerViewController:didFindMatch:
delegate method creates an STGame object and sends it a
-startMultiplayerWithMatch: message.
5. The STGame object sends the remote app “game start” data. When it receives the corresponding “game start” data from the remote app, the game begins.
The next few sections will add this code to your app. Once that matching code is in place, you’ll move on to the actual communications code.
Requesting a Match
Your game begins by requesting a match (connection) with one or more other users running the same app. It does this through a GKMatchRequest object. There are three types of matches you can request: peer-to-peer, hosted,and turn-based.
n Peer-to-peer sets up a direct communications link with all of the other devices.
All of the participants are “peers” that communicate freely with one another.
SunTouch will use peer-to-peer communications.
n A hosted match requires that your app provide its own network connection and communications. It’s intended for games that use a centralized server (like an MMORPG) or one for which you’ve alreadywritten custom communications.
n Turn-based games do not require a direct connection with the other players.
Infrequent communications are relayed—via the Game Center servers—to the other players, allowing for casual game play over distances limited only by
the reach of the Internet. That means you could play a turn-based game with someone on the International Space Station, since they have Internet now.
Begin the process by requesting a match when the game starts. Select the STGameViewController.m
file, find the -startGame method, and add the new code in bold:
- (void)startGame
{
if (self.game==nil)
{
STGame *game = [STGame new]; self.game = game;
[self.gameView reset]; [self.opponentGameView reset]; if (self.twoPlayer)
{
GKMatchRequest *request = [GKMatchRequest new]; request.minPlayers = 2;
request.maxPlayers = 2; request.defaultNumberOfPlayers = 2;
GKMatchmakerViewController *mmvc;
mmvc = [[GKMatchmakerViewController alloc] initWithMatchRequest:request]; mmvc.matchmakerDelegate = self;
[self presentViewController:mmvc animated:YES completion:nil];
} else
{
[self.gameView observeNotificationsFromGame:game]; [game startSinglePlayer];
[self startStrikeGrowAnimation];
}
}
}
The modified -startGame method starts the single-player game immediately. When twoPlayer is YES, it begins the matchmaking process. The match request is configured to limit the minimum, maximum, and default number ofparticipants. Since SunTouch is strictly a one-on-one game, the only choice is 2 players. A peer-to-peer match is established using the GKMatchmakerViewController. The code for this is simple: you create the view controller, make your object its delegate, and present it to the user.
For this to work, your matchmaker delegate object must conform to the GKMatchmakerViewControllerDelegate protocol, so hop over to STGameViewController.h and add that (new code in bold):
@interface STGameViewController : UIViewController <UIAlertViewDelegate, GKMatchmakerViewControllerDelegate>
Completing the Match
Your app handles the success, or failure, of the match by implementing the GKMatchmakerViewControllerDelegate methods. Start by adding the success method to STGameViewController.m:
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFindMatch:(GKMatch *)match
{
[self dismissViewControllerAnimated:YES completion:nil]; if (match.expectedPlayerCount==0)
{
[self.game startMultiPlayerWithMatch:match started:^{ [self.gameView observeNotificationsFromGame:self.game]; [self.opponentGameView observeNotificationsFromGame:self.game]; [self startStrikeGrowAnimation];
}];
}
}
When a match is established, the matchmaker view controller creates a GKMatch object and your delegate receives a -matchmakerViewController:didFindMatch: message. The match object is the one you’ll use to communicatewith the remote players.
It may take awhile for all of the players to connect, and you may receive this message multiple times. You should examine the expectedPlayerCount property of the match object. It reports the number of players you’re still waiting(expecting) to connect with. Once it is 0, all of the players have connected. This is the point where SunTouch starts the game engine.
But the game still hasn’t started yet! Before a SunTouch bout can begin, the game engine must first communicate with the other player to establish the parameters of the game (where the suns are hidden). You’ll get to thatlater. For now, just know that -startMultiplayerWithMatch: begins the process of exchanging variables with the remote app and synchronizing the start of gameplay. This is called a handshake. When the handshake is finished, the game starts and the code block you passed in the started: parameter is executed, allowing the STGameViewController to perform its start-of-game housekeeping at the same time.
Finally, add the two failure delegate methods:
- (void)matchmakerViewControllerWasCancelled:(GKMatchmakerViewController *)controller
{
[[NSNotificationCenter defaultCenter] postNotificationName:kGameDidEndNotifcation object:self];
}
- (void)matchmakerViewController:(GKMatchmakerViewController *)controller didFailWithError:(NSError *)error
{
[self matchmakerViewControllerWasCancelled:controller];
}
A -matchmakerViewControllerWasCancelled: message is received if the player decides they don’t want to connect with another player. A -matchmakerViewController:didFailWithError: message
is received if something went wrong. Both end the game, dismissing the game view controller, and returning to the initial screen.
Exchanging Data with Another Device
This is where the rubber meets the road, so to speak. The GKMatch object returned by the matchmaker is your conduit for communicating with the other iOS devices. At its core, it’s ridiculously simple to
use. It has a delegate property and some -sendData... methods. The -sendData... methods send data to the other devices. Your delegate object receives a -match:didReceiveData:fromPlayer: message when those other devices senddata to your app. That sounds simple, doesn’t it? The devil is in the details.
As the app designer, you must decide what information to send, how it’s formatted, how the receiver will interpret it, which players you’re going to send the data to, when to send the data, and how important it is that said datais received.
When designing your communications, there are a variety of ways you can organize it. There are methods to send data to all of the other participants, or to just one. This lets you choose to send updates to all of the otherplayers, or specific updates to individual players. Your game might require that one device be designated as the host or master (for games structured like Dungeons and Dragons). The GKMatch class has a -chooseBestHostPlayerWithCompletionHandler: method for assisting the devices in choosing a ringleader.
In this respect, SunTouch is simple. There’s never more than one other player, so communication topology isn’t an issue. The only tricky part is deciding which app will pick the random locations for the suns. Both playersmust be using the same set of sun locations, or the game won’t make
any sense—or at least less sense than it already does. (How do you make a hole in space, and why would you want to?)
Remember that you have two identical versions of the program running, simultaneously, on two separate devices. There’s no “leader” unless you pick one. For SunTouch, the solution is for both apps to pick a set of random sun locations. They then toss a coin—or the electronic equivalent—and choose a winner. Both apps will use the winner’s set of suns. After that, your app sends data to
the remote app describing strikes and captured suns. The remote app is, simultaneously, sending your app the opposing player’s strikes and captured suns. SunTouch’s communications can be summarized as follows:
n Send “game start” data to the remote app containing a list of random sun locations along with a random number (the “coin”).
n When “game start” data is received, compare the remote app’s random number to ours. This determines who wins the coin toss and which set of sun locations will be used.
n When the user initiates a strike, send “strike” data to the remote app containing the location and radius of the strike.
n When “strike” data is received from the remote app, animate a strike in the opponent’s game view.
n When the local player captures a sun, send “sun captured” data to the remote app, including the time the sun will be captured.
n When “sun captured” data is received from the remote app, animate the captured sun and credit the opposing player. If both players uncover the same sun, the earliest capture wins.
All of the communications code is going into STGame. It’s the two game engine objects that talk to each other. Your controller objects are only concerned with interaction with the local player, and the view objects just respondto notifications. You’ll start to implement this by fleshing out the -startMul tiPlayerWithMatch:started: method and then construct the individual send/receive data methods.
Starting the Game
Click on the STGame.h interface file. You’re going to add some variables and a multi-player start method. Start with the variables. Edit the instance variable section of the @interface so it looks like this (new code in bold):
@interface STGame : NSObject
{
NSArray* suns; NSTimeInterval startTime;
GKMatch *multiPlayerMatch;
void (^multiPlayStarted)(void); uint32_t coinToss;
}
The multiPlayerMatch variable keeps a reference to the GKMatch object you need to communicate with the other apps. The funny-looking declaration after that defines the multiPlayerStarted variable. It’s a code block variable; itholds a reference to a block of code that STGame can later execute. This is one of the parameters of the -startMultiPlayerWithMatch:started: method. Speaking of which, add a method declaration for it after -startSinglePlayer:
- (void)startSinglePlayer;
- (void)startMultiPlayerWithMatch:(GKMatch*)match started:(void(^)(void))started;
Switch to STGame.m and write the new method:
- (void)startMultiPlayerWithMatch:(GKMatch*)match started:(void(^)(void))started
{
multiPlayerMatch = match; multiPlayStarted = started; suns = [STGame randomSuns]; coinToss = arc4random(); match.delegate = self;
[self sendGameStart];
}
The GKMatch object you’re going to use to communicate with the other apps is saved, along with the reference to the code block to execute when the game actually gets underway. Next, a set of random sun locations is generated along with a random number that will act as the coin toss. The app that picks the highest random number is the one that determines the sun locations.
The STGame object is made the delegate for the GKMatch object. Data received by the match object will now be sent to STGame. Finally “game start” data is sent to the remote app. Presumably, the remote app is executing theexact same code, at nearly the same time, choosing a set of suns, a random number, and sending this app its “game start” data.










No comments:
Post a Comment