Creating a Cross-Platform Multiplayer Game in Unity — Part 4

In this final part of the series, you will deal with clients sending messages out of order and the various ways on how to deal with them. By Todd Kerpelman.

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

Cancelling Auto-Matching

What if your player gets tired of waiting for people to play with and wants to play a single-player game instead? To solve that, you will add a cancel button to the auto-match dialog.

Leaving a room you're waiting in is as simple as calling Google Play Games platform's LeaveRoom(), which you've already implemented as LeaveGame() in MultiplayerController.

Open MainMenuScript.cs; find the following code in OnGUI():

if (_showLobbyDialog) {
	GUI.skin = guiSkin;
	GUI.Box(new Rect(Screen.width * 0.25f, Screen.height * 0.4f, Screen.width * 0.5f, Screen.height * 0.5f), _lobbyMessage);
}

...and replace it with the following:

if (_showLobbyDialog) {
	GUI.skin = guiSkin;
	GUI.Box(new Rect(Screen.width * 0.25f, Screen.height * 0.4f, Screen.width * 0.5f, Screen.height * 0.5f), _lobbyMessage);
	if (GUI.Button(new Rect(Screen.width * 0.6f, 
                                Screen.height * 0.76f, 
                                Screen.width * 0.1f, 
                                Screen.height * 0.07f), "Cancel")) {
		MultiplayerController.Instance.LeaveGame();
		HideLobby();
	}
}

You call HideLobby() right away instead of calling it from a listener method in your MultiplayerController. In many cases, when you leave a room in the middle of setting it up you won't get a callback in the form of OnLeftRoom(). It's more likely to surface as an OnRoomConnected(false) error.

Build and run your game; start a multiplayer game, then cancel it and watch that dialog disappear!

Note: Don't be tempted to start and cancel games in rapid succession, or you may find yourself in a weird state where you can't join any games for about ten minutes or so. Google engineers tell me this is because quickly joining and canceling a room too many times could get you into a weird state where the service thinks your client is broken and starts to rate-limit you.

Personally, I suspect it's because the service is a little ticked off that you were messing with it and it's giving you the silent treatment in an insolent kind of way. It's sensitive like that. :]

Note: Don't be tempted to start and cancel games in rapid succession, or you may find yourself in a weird state where you can't join any games for about ten minutes or so. Google engineers tell me this is because quickly joining and canceling a room too many times could get you into a weird state where the service thinks your client is broken and starts to rate-limit you.

Personally, I suspect it's because the service is a little ticked off that you were messing with it and it's giving you the silent treatment in an insolent kind of way. It's sensitive like that. :]

Adding More Players

Not everybody has three devices they can develop with, but if you do, then you can easily update your game to permit more than two players!

To start, replace StartMatchMaking in MultiplayerController with the following:

private void StartMatchMaking() {
    PlayGamesPlatform.Instance.RealTime.CreateQuickGame(2, 2, 0, this);
}

The next step is...oh, wait, that's all you have to do! :] Now you understand why you used those dictionary lists to track opponents.

Build and run your project on three different devices and play to your heart's content! In fact, there's nothing to stop you from making this a four-player game...except for the fact that I ran out of car artwork. :]

3 Player Awesomeness

The first two arguments to CreateQuickGame means the game will accept a minimum of two and a maximum of two other opponents. If you changed that call to CreateQuickGame(1, 2, 0, this), your game would try to find a three-player game, but it would start a two-player game if it didn't find enough opponents in time.

If you're having a problem getting all three devices into a game, check the two following things — which both happened to me as I wrote this tutorial:

  1. Make sure you're not signed in with the same account on two (or all) devices.
  2. Make sure you're signed in with an account that has been added as a tester.
At this point, the actual tutorial part of the ends and the following sections is a fascinating look at various aspects of game networking

Synchronizing Game Start

Depending on your devices and how quickly they receive all the setup information from Play Game services, you may have noticed that not all of your games start at exactly the same time.

This doesn't end up affecting the fairness of the game, because each individual client reports back the total time it took them to go around the track, regardless of when they actually started the race. But it does mean that it might look like you finished your game earlier than your opponent — only to find out your opponent had a better race time than you.

It's a bit tricky to get your players to start at the same time, but one potential solution could look like this:

  1. Choose your "host"; the easiest way is to pick the player listed first in your GetAllPlayers() call, which all clients should agree upon since this call returns a sorted list of participant IDs.
  2. Have your host send out ping messages to all the other players. These messages would likely include the local time in milliseconds and, perhaps, a numeric ID.
  3. Have all your other players reply back to these pings. These replies would include the original send time.
  4. When your host receives these replies, it can compare its current time to when it sent the original message, divide by 2 to get the one-way trip time, and it will know approximately how long it takes for a message to make the round-trip from the host to the other player.
  5. Repeat this until your player receives about seven or eight replies from all other players to calculate a decent average ping time.
  6. Once that's done, you can create a synchronized start by looking at the longest ping time, adding a little buffer, and then sending out a message to each player telling them to start.

For example, suppose you're the host and you find it takes 200 ms to send a message to Player 1 and 25 ms to Player 2. You could send a message to Player 1 saying "start the game 50 ms from now", a message to player 2 saying "start the game 225 ms from now" and a message to yourself saying, "Start the game 250 ms from now". That would make all three players start at approximately the same time.

Here's a diagram showing this process:

Synchronized Start

One complication is that this kind of timing test only works if you're using unreliable messaging; the timing of reliable messaging varies too much to be of use. So your clients need to send a "Yeah, I got the game start message; stop bothering me about it" reply to your host, and your host needs to keep telling the other players to start (adjusting the start time offset along the way) until the host has confirmed that everybody received the start message.

That's a lot of work just to ensure all players start at the same time. However, if there's enough interest in seeing a fully coded solution, let us know and perhaps we can add it as a follow-up tutorial.

Todd Kerpelman

Contributors

Todd Kerpelman

Author

Over 300 content creators. Join our team.