Update the Game Scene to Send the Match Data

Now that you’ve got the methods in place to handle passing the game data back and forth between players, you need to update the rest of the project code to use those methods. You also need to add a way for players to launch a multiplayer match.

To speed up the process, use the Find Navigator to search for the TODO marks, like so:

images/CreatingMultiplayerGamesWithGameKit/find-todos.png

Now that you know which files need your attention, you’re ready to begin.

Updating the Scene Manager

Working from the bottom up, select the second TODO listed in the SKScene+SceneManager.swift file. This will open the SKScene+SceneManager.swift file in the Source Editor, with the corresponding TODO highlighted, as shown here:

images/CreatingMultiplayerGamesWithGameKit/xcode-first-todo.png

As you add the necessary code to this file, you’ll receive some warnings and errors. These errors and warnings are expected, so you can ignore them for now. If the errors become too distracting, or you wish to rely on Xcode’s autocomplete functionality, you can scroll up to the top of this file now and import the GameKit framework; otherwise, you’ll add this import later.

The first step is to add a new method to the scene manager that handles loading multiplayer games. Replace the line that reads // TODO: Add code to load Game Center game with the following code:

 func​ ​loadGameCenterGame​(match: ​GKTurnBasedMatch​) {
 print​(​"Attempting to load the game scene using Game Center match data."​)
 
 }

This is the start of your new method. You’ll call this method when the player launches a multiplayer Game Center game.

Below the print statement, add the following code:

 match.​loadMatchData​(completionHandler: {(data, error) ​in
 
 })

This code calls the loadMatchData(completionHandler:) method on the GKTurnBasedMatch object. This method is responsible for fetching the match data for the match.

Next, inside the completion handler, add the following code:

 // Set current match data
 GameKitHelper​.shared.currentMatch = match
 
 // Set up the Game Center data model
 var​ gcDataModel: ​GameCenterData
 
 // If available, use the match data to set up the model
 if​ ​let​ data = data {
 do​ {
  gcDataModel = ​try​ ​JSONDecoder​().​decode​(​GameCenterData​.​self​,
  from: data)
  } ​catch​ {
  gcDataModel = ​GameCenterData​()
  }
 } ​else​ {
  gcDataModel = ​GameCenterData​()
 }

Here, you’re setting the GameKitHelper.shared.currentMatch property using the match object, and you’re also creating the GameCenterData object either by decoding the existing data or creating new data.

Below the code you just added, and still within the completion handler, add the following code:

 // Set up the players and mark the local player
 for​ participant ​in​ match.participants {
 
 }

This for-in loop is where you’ll loop through the participants to find and set the local player.

Next, inside the for-in loop, add this code:

 if​ ​let​ player = participant.player {
 
 // Create the gc player object
 let​ gcPlayer = ​GameCenterPlayer​(playerId: player.gamePlayerID,
  playerName: player.displayName)
 
 // Check if this is the local player
 if​ player == ​GKLocalPlayer​.local {
  gcPlayer.isLocalPlayer = ​true
  }
 
 // Check for a winner
 if​ participant.matchOutcome == .won {
  gcPlayer.isWinner = ​true
  }
 
 // Add gc player to the gc model
  gcDataModel.​addPlayer​(gcPlayer)
 }

Here, you’re setting up the players of the game and marking some key properties, like isLocalPlayer and isWinner.

Finally, outside the for-in loop, but still inside the completion handler, add the following code:

 // Load the game scene
 self​.​loadGameScene​(gameType: .remoteMatch,
  matchData: gcDataModel, matchID: match.matchID)

At this point, you’ll get another error about “extra arguments at positions #2, #3 in call.” You’ll take care of that error in a moment. In the meantime—and for reference—the entire method you just added looks like this:

 func​ ​loadGameCenterGame​(match: ​GKTurnBasedMatch​) {
 print​(​"Attempting to load the game scene using Game Center match data."​)
 
  match.​loadMatchData​(completionHandler: {(data, error) ​in
 // Set current match data
 GameKitHelper​.shared.currentMatch = match
 
 // Set up the Game Center data model
 var​ gcDataModel: ​GameCenterData
 
 // If available, use the match data to set up the model
 if​ ​let​ data = data {
 do​ {
  gcDataModel = ​try​ ​JSONDecoder​().​decode​(​GameCenterData​.​self​,
  from: data)
  } ​catch​ {
  gcDataModel = ​GameCenterData​()
  }
  } ​else​ {
  gcDataModel = ​GameCenterData​()
  }
 
 // Set up the players and mark the local player
 for​ participant ​in​ match.participants {
 if​ ​let​ player = participant.player {
 
 // Create the gc player object
 let​ gcPlayer = ​GameCenterPlayer​(playerId: player.gamePlayerID,
  playerName: player.displayName)
 
 // Check if this is the local player
 if​ player == ​GKLocalPlayer​.local {
  gcPlayer.isLocalPlayer = ​true
  }
 
 // Check for a winner
 if​ participant.matchOutcome == .won {
  gcPlayer.isWinner = ​true
  }
 
 // Add gc player to the gc model
  gcDataModel.​addPlayer​(gcPlayer)
  }
  }
 
 // Load the game scene
 self​.​loadGameScene​(gameType: .remoteMatch,
  matchData: gcDataModel, matchID: match.matchID)
  })
 }

All right, time to fix the error. Do you see that little red X in the error? It looks something like this:

images/CreatingMultiplayerGamesWithGameKit/xcode-extra-args.png

Click that little red X and you’re taken to the top of the file where you’ll see something like this:

images/CreatingMultiplayerGamesWithGameKit/xcode-extra-args-mark.png

What’s happening here is that you’re passing in arguments that don’t yet exist. To fix this problem, you need to change the method signature for func loadGameScene(gameType: GameType) { to:

 func​ ​loadGameScene​(gameType: ​GameType​,
  matchData: ​GameCenterData​? = ​nil​,
  matchID: ​String​? = ​nil​) {

While you’re here, if you didn’t already add the import statement for the GameKit framework, you can add it now just below the line that reads import GameplayKit, like so:

 import​ ​GameKit

Now, scroll down to the next TODO that reads // TODO: Add code to populate Game Center match data and replace it with the following two lines of code:

 sceneNode.gameCenterData = matchData
 sceneNode.gameCenterMatchID = matchID

You’ll get another two errors about the GameScene class missing these two “members,” but you’ll take care of fixing that problem momentarily. For now, save the file and move on to the next batch of TODOs in the Find Navigator panel.

If you’d like, you can refresh the search results so that you’re looking only at the remaining TODOs.

Updating the Lobby Scene

For now, skip the GameScene.swift file TODOs and focus on the TODOs in the LobbyScene.swift file; you’ll find them in the didMove(to:) method.

Once again, you can either disregard the errors you’ll get as you add the new code, or you can scroll up to the top of the LobbyScene.swift file and import the GameKit framework. Either way, you’ll be reminded to import the framework later in this section.

In the didMove(to:) method, you’ll see both of the TODOs in this file. Replace those TODOs with the following code:

 // Reset current match data
 GameKitHelper​.shared.currentMatch = ​nil
 
 // Set up GC Remote Game notification
 NotificationCenter​.​default​.​addObserver​(
 self​,
  selector: ​#selector(​​self.processGameCenterRequest​​)​,
  name: .receivedTurnEvent, object: ​nil​)

You’ll get another error about a missing member, which will look something like this:

images/CreatingMultiplayerGamesWithGameKit/xcode-error-method.png

To fix this “missing member” error, add a new method below the didMove(to:) method:

 @objc​ ​func​ ​processGameCenterRequest​(_ notification: ​Notification​) {
 guard​ ​let​ match = notification.object ​as?​ ​GKTurnBasedMatch​ ​else​ {
 return
  }
 
 loadGameCenterGame​(match: match)
 }

And, of course, to fix the error related to the missing GKTurnBasedMatch type, import the GameKit framework, if you haven’t done so already.

Save the file and get ready to resolve the final list of TODOs in the GameScene.swift file.

Updating the Game Scene

There are six TODOs in the GameScene.swift file. This time, before you get started, import the GameKit framework:

 import​ ​GameKit

Taking each TODO one at a time, replace the line that reads // TODO: Add Game Center-related properties with the following code:

 // Multiplayer game properties
 var​ gameCenterMatchID: ​String​?
 var​ gameCenterData: ​GameCenterData​?

You’ll use these properties to store the Game Center match data. Adding these properties also resolve the error your were getting in the SKScene+SceneManager.swift file.

Next, replace the line that reads // TODO: Add Game Center Observers, with the following code:

 // Set up GC Remote Game notification
 NotificationCenter​.​default​.​addObserver​(
 self​,
  selector: ​#selector(​​self.processGameCenterRequest​​)​,
  name: .receivedTurnEvent, object: ​nil​)

You’ll receive the following error:

images/CreatingMultiplayerGamesWithGameKit/xcode-error-method-gamescene.png

To fix that error, replace the line that reads // TODO: Add Game Center Notification Handlers with the following code:

 @objc​ ​func​ ​processGameCenterRequest​(_ notification: ​Notification​) {
 guard​ ​let​ match = notification.object ​as?​ ​GKTurnBasedMatch​ ​else​ {
 return
  }
 
 if​ gameCenterMatchID == match.matchID {
 loadGameCenterGame​(match: match)
  } ​else​ {
 print​(​"Player is playing a different game."​)
  }
 }

All right, on to the next TODO.

In the setupRemoteGame() method, replace the line that reads // TODO: Add code to set up the game with the following code:

 // Check if it's the local player's turn
 if​ ​GameKitHelper​.shared.​canTakeTurn​() == ​false​ {
 print​(​"It's the remote player's turn."​)
 
 // Move play to the next player in the game model
  gameModel.​nextPlayer​()
 
 // Visually disable pass and roll buttons
  rollButton.texture = rollButtonTextureDisabled
  passButton.texture = passButtonTextureDisabled
 }

This code first verifies that it’s not the local player’s turn, and if that’s the case, it moves play to the next (remote) player; otherwise, no action is needed.

Next, you need to update the players’ scores. Starting with the local player, add the following code below the code you just added:

 // Update local player scoreboard
 if​ ​let​ localPlayer = gameCenterData?.​getLocalPlayer​() {
  gameModel.players[0].totalPoints = localPlayer.totalPoints
 if​ localPlayer.isWinner == ​true​ {
  gameModel.currentPlayerIndex = 0
 endGame​()
  }
 }

Now, the remote player:

 // Update remote player scoreboard
 if​ ​let​ remotePlayer = gameCenterData?.​getRemotePlayer​() {
  gameModel.players[1].totalPoints = remotePlayer.totalPoints
 if​ remotePlayer.isWinner == ​true​ {
  gameModel.currentPlayerIndex = 1
 endGame​()
  }
 }

Moving right along to the next TODO in the processEndTurnForRemoteGame() method, find the line that reads // TODO: Add code to end the turn, and replace it with this:

 // Update the local player's stats
 if​ ​let​ localPlayer = gameCenterData?.​getLocalPlayer​() {
 if​ ​let​ index = gameCenterData?.​getPlayerIndex​(for: localPlayer) {
  gameCenterData?.players[index].totalPoints =
  gameModel.players[0].totalPoints
  }
 }
 
 // Update the remote player's stats
 if​ ​let​ remotePlayer = gameCenterData?.​getRemotePlayer​() {
 if​ ​let​ index = gameCenterData?.​getPlayerIndex​(for: remotePlayer) {
  gameCenterData?.players[index].totalPoints =
  gameModel.players[1].totalPoints
  }
 }
 
 // End the turn and send the data
 GameKitHelper​.shared.​endTurn​(gameCenterData!)
 
 // Switch back to the remote player? Yes.
 if​ ​GameKitHelper​.shared.​canTakeTurn​() == ​false​ {
 // Visually disable pass and roll buttons
  rollButton.texture = rollButtonTextureDisabled
  passButton.texture = passButtonTextureDisabled
 }

Finally, you need to add the code to the endGame() method to clear up the last TODO. Find the line that reads // TODO: Add code to end Game Center game, and replace it with the following code:

 if​ gameType == .remoteMatch && isWinner == ​true​ {
 GameKitHelper​.shared.​winGame​(gameCenterData!)
 } ​else​ ​if​ gameType == .remoteMatch && isWinner == ​false​ {
 GameKitHelper​.shared.​lostGame​(gameCenterData!)
 }

Fantastic, you have everything in place, and you’re ready to test the game.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset