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 4 of this article. Click here to view the first page.

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.)

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.

Leaving the Game (Less Normally)

It's a fact of life that all players won't see a game through to completion. This can happen intentionally, where a player rage-quits, has to go to an appointment, or just gets fed up with an opponent who's not playing fair; or unintentionally, where your game crashes, the player loses their network connection, or their battery dies. Your job as a game designer is to handle these unexpected quits in a clean manner.

Take the case where a player consciously leaves the game. Do you really want to encourage players to rage-quit? No, but there are lots of valid reasons someone might leave a game; many events in the real world often require you to put your device away for a moment, such as saying your wedding vows. :] To that end, you'll add a "Leave Game" button in your UI.

Open GameController.cs and add the following code to the beginning of OnGUI(), just outside of the if block:

if (_multiplayerGame) {
    if (GUI.Button (new Rect (0.0f, 0.0f, Screen.width * 0.1f, Screen.height * 0.1f), "Quit")) {

        // Tell the multiplayer controller to leave the game
        MultiplayerController.Instance.LeaveGame();
    } 
}

This calls LeaveGame() in MultiplayerController, which in turn calls LeaveRoom() in the Google Play platform. Once your player has left the room, the platform reports this back to your game in the OnLeftRoom() callback, which will then call LeaveGameConfirmed() in your Game Controller, just as in the diagram shown earlier.

Note: This is actually a pretty terrible way for a player to quit the game. At the very least, you'd want to put up a confirmation dialog so that a player can't accidentally leave the game with a single tap on the screen.

But what about the players remaining in the game? They need to know that this player has left, so they're not sitting around waiting for him or her to finish. Well, if you've been carefully studying your console log (which is what I like to do for fun on a Saturday night), you likely saw a line like this when your opponent left the game:

Player p_CL3Ay7mbjLn16QEQAQ has left.

This means this event is being captured in your OnPeersDisconnected() listener; therefore you just need to pass that information back to the game.

Note: At the time of this writing, there is an "undocumented feature" (okay, fine, a bug) in the library where the room is considered destroyed if your opponent leaves the room and you're the only one left. You'll also receive an OnLeftRoom callback instead of an OnPeersDisconnected call. Hopefully that "feature" has been dealt with by the time this is published! :]

First, add the following line to your MPUpdateListener interface in MPInterfaces.cs file:

void PlayerLeftRoom(string participantId);

MultiplayerController will call this method when it receives a notice that somebody left the room.

Next, modify OnPeersDisconnected in MultiplayerController.cs, as follows:

public void OnPeersDisconnected (string[] participantIds)
{
    foreach (string participantID in participantIds) {
        ShowMPStatus("Player " + participantID + " has left.");
        if (updateListener != null) {
            updateListener.PlayerLeftRoom(participantID);
        }
    }
}

This loops through each player and calls your new interface method on your listener. Open GameController.cs and add that method now:

public void PlayerLeftRoom(string participantId) {
    if (_finishTimes[participantId] < 0) {
        _finishTimes[participantId] = 999999.0f;
        CheckForMPGameOver();
    }
}

When a player leaves a room, you record their finish time as 999999.0; this ensures CheckForMPGameOver counts this player as "finished" since the finish time is positive. Using such a large value means a player can't cheat their way to first place by quitting a game early.

The call to CheckForMPGameOver() is necessary since the disconnected player won't ever send a "game over" message; if they were the only player remaining in the game you'd want the game to end at this point.

Note: Depending on your game design, you might want to end the game early if there is only one player remaining. In Circuit Racer, it's still fun to drive around a track by yourself, so you let the local player finish off the race. On the other hand, a first person shooter would be much less fun if there were nobody left to shoot, so leaving the game early would be a better choice.

Build and run your game; start a game and end one client early by tapping the Quit button. The other player will be able to finish their game and start a new one instead of waiting around forever.

It looks a little odd to see your disconnected opponent's dead car sitting in the middle of the track, but it's easy enough to add some code to remove it from the game.

Open OpponentController.cs and add the following method:

public void HideCar() {
    gameObject.renderer.enabled = false;
}

In PlayerLeftRoom, add the following code just before CheckForMPGameOver():

if (_opponentScripts[participantId] != null) {
    _opponentScripts[participantId].HideCar();
}

Build and run your game; start a two-player game and quit a game prematurely. The opponent's car will eventually vanish from the screen, so you know your opponent is truly gone and not just being difficult by sulking in the middle of the road. :]

So are we done now?

Close — but not yet. You've handled the situation where a player intentionally leaves the room and the Google Play game services library calls LeaveRoom on behalf of that player. Sometimes, though, the service doesn't have the chance to make a clean exit — and that's the next scenario you'll need to handle.

Todd Kerpelman

Contributors

Todd Kerpelman

Author

Over 300 content creators. Join our team.