Creating a Cross-Platform Multi-Player Game in Unity — Part 3

In the third part of this tutorial, you’ll learn how to deal with shaky networks, provide a winning condition, and deal with clients who exit the session. By Todd Kerpelman.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 5 of this article. Click here to view the first page.

Receiving the Game Over Call

Now you'll need some logic to handle this "game over" message.

Still working in MultiplayerController, add the following code to the end of the if (messageType == 'U' ... block in OnRealTimeMessageReceived:

} else if (messageType == 'F' && data.Length == _finishMessageLength) {
    // We received a final time!
    float finalTime = System.BitConverter.ToSingle(data, 2);
    Debug.Log ("Player " + senderId + " has finished with a time of " + finalTime);    
}

This code checks to see if the message is a game-over message, at which point, it parses the final time and prints out a log message.

Build and run your game; race your cars around the track and once a player completes three laps, they'll come to a stop and a message similar to Player p_CPT-6O-nlpXvVBAB has finished with a time of 15.67341 message should appear in your console log.

Note: To speed up testing, set _lapsRemaining to 1 in SetupMultiplayerGame of GameController.cs. But don't forget to set it back when you want to play a real game! :]

Although the cars have stopped, you still need to handle the end-game logic and figure out who won.

Add the following line to MPUpdateListener in MPInterfaces:

void PlayerFinished(string senderId, float finalTime);

This declares that PlayerFinished is part of the MPUpdateListener interface.

Now you need to handle this information in GameController! Add the followiong variable to GameController.cs:

private Dictionary<string, float> _finishTimes;

This sets up a dictionary to map the finish times to participantIDs.

Inside of SetupMultiplayerGame(), add the following line before the start of the for-loop:

_finishTimes = new Dictionary<string, float>(allPlayers.Count);

Just inside the for-loop, add the following the line of code just underneath the first line like so:

for (int i =0; i < allPlayers.Count; i++) {
    string nextParticipantId = allPlayers[i].ParticipantId;             
     _finishTimes[nextParticipantId] = -1;   // <-- New line here! 
    ...

You initialize each entry with a negative number, which is an easy way to indicate that this player hasn't finished yet.

Next, add the following method to GameController.cs:

public void PlayerFinished(string senderId, float finalTime) {
    Debug.Log ("Participant " + senderId + " has finished with a time of " + finalTime);
    if (_finishTimes[senderId] < 0) {
        _finishTimes[senderId] = finalTime;
    }
    CheckForMPGameOver();
}

This simply records the finishing time of this player in the dictionary.

Next, add the following method underneath the previous one:

void CheckForMPGameOver() {
    float myTime = _finishTimes [_myParticipantId];
    int fasterThanMe = 0;
    foreach (float nextTime in _finishTimes.Values) {
        if (nextTime < 0) { // Somebody's not done yet
            return; 
        }
        if (nextTime < myTime) {
            fasterThanMe++;
        }
    }
    string[] places = new string[]{"1st", "2nd", "3rd", "4th"};
    gameOvertext = "Game over! You are in " + places[fasterThanMe] + " place!";
    PauseGame(); // Should be redundant at this point
    _showingGameOver = true;
    // TODO: Leave the room and go back to the main menu
}

In the code above you're iterating through the finish times of all of the players in your dictionary. If any of them are negative, it means they haven't finished yet and you can jump out early. Otherwise, you keep track of how many finish times are faster than the local player's so you can display the appropriate game over text. Then you set _showingGameOver to true so that your OnGUI() method knows to display the game over dialog box.

Next, add the following line to OnRealTimeMessageReceived() in MultiplayerController.cs, just after the Debug.Log line in the else-if block:

updateListener.PlayerFinished(senderId, finalTime);

Finally, you need to tell the local device that the game is done, as calling SendMessageToAll() doesn't send a message to the local player.

Open GameController.cs and in Update(), add the following code directly underneath the MultiplayerController.Instance.SendFinishMessage(_timePlayed); line:

PlayerFinished(_myParticipantId, _timePlayed);

Build and run your game on both devices; race both cars around the track and you should now have a lovely game over dialog! ...and once again, your game is stuck.

Game Over Dialog

It's high time that this game has a proper exit strategy! Fortunately, that's your very next task. :]

Leaving a Game (Normally)

Leaving a multiplayer game is quite straightforward; you simply call the Google Play platform's LeaveRoom() method.

Add the following line to the end of CheckForMPGameOver in GameController.cs, where the TODO line is:

Invoke ("LeaveMPGame", 3.0f);

This calls the unwritten LeaveMPGame after a three-second pause.

Add that method next:

void LeaveMPGame() {
    MultiplayerController.Instance.LeaveGame();
}

Now you can add the missing LeaveGame() call to MultiplayerController.cs as follows:

public void LeaveGame() {
    PlayGamesPlatform.Instance.RealTime.LeaveRoom ();
}

So that disconnects you from the Google Play game room — but you haven't yet instructed your game client to do anything different, so your game will stay stuck on the game over screen.

There are a few ways to fix this; you could simply load the MainMenu scene as soon as you call LeaveMPGame. That would work, but a better method is to wait until OnLeftRoom() in MultiplayerController is called by the platform. At that point, you know you've left the room for good and your Game Controller can perform any necessary cleanup tasks.

Another advantage of this approach is that if you added other ways to leave the multiplayer room, they'd all be caught and handled by this same code path, which prevents against future redundant code.

Here's a quick diagram explaining all this:

This is the full series of calls that are made when you leave a room. (The ones in gray are handled by the library.)

This is the full series of calls that are made when you leave a room. (The ones in gray are handled by the library.)

This is the full series of calls that are made when you leave a room. (The ones in gray are handled by the library.)

Add the following line to MPUpdateListener in MPInterfaces:

void LeftRoomConfirmed();

This defines the interface to let any listeners know that the player has left the room.

Then, modify OnLeftRoom() in MultiplayerController.cs as follows:

public void OnLeftRoom ()
{
    ShowMPStatus("We have left the room.");
    if (updateListener != null) {
        updateListener.LeftRoomConfirmed();
    }
}

Once this method is called, it is safe to call LeftRoomConfirmed() on the delegate.

Finally, add the following method to GameController.cs:

public void LeftRoomConfirmed() {
    MultiplayerController.Instance.updateListener = null;
    Application.LoadLevel ("MainMenu");
}

Once this method is called, it is safe to move the player from the current scene, to the MainMenu scene.

Build and run your game; race around the track and when you finish, you should see the game over dialog box. After about three seconds, you'll find yourself at the main menu ready to play another game!

So, are we done?

Before you call it a day, you should test some networking edge cases, such as losing a connection altogether. Start up another game on both devices and kill the app using the task manager on one device partway through the race.

Finish the race on the other device — but as soon as your car finishes, it will wait there forever, waiting for its opponent to finish.

Todd Kerpelman

Contributors

Todd Kerpelman

Author

Over 300 content creators. Join our team.