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

Leaving the Game (Abnormally)

If your player's battery dies, their game crashes, lose their cell service in the subway, or drop their phone in the toilet (it's been known to happen), your game won't have an opportunity to leave the room properly.

Try this yourself — no, don't go and drop your phone in the toilet. :] You can turn on airplane mode on one of your devices while in the middle of the game, which will kill your network connectivity, not give the Google Play games services library a chance to call LeaveRoom(), and leave the other player waiting forever for the other player to either send a "game over" message or leave the room — neither of which will happen.

The most common way to detect these scenarios is through timeouts. You're receiving approximately six updates per second for your opponent; if those calls stopped for long enough, you could probably assume something terrible has happened to the other player. Or that their game has crashed. One of the two.

Our time-out strategy

Your strategy for detecting timeouts will be pretty straightforward. Each CarOpponent will keep track of the last time they received a game update. You'll check at intervals to see if this update is longer than your timeout threshold. If it is, treat the opponent as you would if they left the game voluntarily.

Aha! OpponentCarController is already storing the last time it received an update from the network in _lastUpdateTime. You just need a way to access it.

Add the following line to OpponentCar, right beneath the spot where you declare _lastUpdateTime:

public float lastUpdateTime { get { return _lastUpdateTime; } }

That code might look a little odd to you, but it's not all that different from creating a readonly property in Objective-C. You simply create a lastUpdateTime property with only a getter (hence making it read-only), and define the getter to return the value of the private variable _lastUpdateTime.

This lets you retrieve the value of _lastUpdateTime from GameController, while only OpponentCar can set the value.

You need to make sure this threshold is realistic; you don't want players timing out right away just because _lastUpdateTime was initialized to 0!

Add the following code to Start():

_lastUpdateTime = Time.time;

This sets the time when an opponent is created.

Now you can add the logic to check for timeouts. Add the following variables to GameController:

public float timeOutThreshold = 5.0f;
private float _timeOutCheckInterval = 1.0f;
private float _nextTimeoutCheck = 0.0f;

The timeOutThreshold is the how many seconds before the player is considered gone. _timeOutCheckInterval is how often the system should check for a timeout. _nextTimeoutCheck holds the time plus the interval so it can be easily checked (versus calculating the total every iteration).

Next, add the following method to GameController:

void CheckForTimeOuts() {
    foreach (string participantId in _opponentScripts.Keys) {
        // We can skip anybody who's finished.
        if (_finishTimes[participantId] < 0) {
            if (_opponentScripts[participantId].lastUpdateTime < Time.time - timeOutThreshold) { 
                // Haven't heard from them in a while!
                Debug.Log("Haven't heard from " + participantId + " in " + timeOutThreshold + 
                          " seconds! They're outta here!");
                PlayerLeftRoom(participantId);
            }
        }
    }
}

First, you iterate through all opponent participantIDs by looking at the keys of the _opponentScripts dictionary. You then check the last time you heard from each player that hasn't finished; if you haven't heard from them in more than 5.0 seconds treat them as if they had left the game.

Note: A five-second value is great for testing, but in real life you'd want to use a value around 10 or 15 seconds, particularly if you're planning on releasing your game internationally to markets where flaky 2G networks are still common.

Finally, you need to call this method from within DoMultiplayerUpdate(). It's probably overkill to call it in every frame, so you'll call it once per second instead.

Add the following code to the beginning of DoMultiplayerUpdate().

if (Time.time > _nextTimeoutCheck) {
    CheckForTimeOuts();
    _nextTimeoutCheck = Time.time + _timeOutCheckInterval;
}

Build and run your game one last time — and ensure you've turned off airplane mode to save pulling of hair and much gnashing of teeth. :] Start a game, then enable airplane mode on one of your devices. After a five-second pause, you should see a note in the console log that this player is gone, and their car should disappear from the track.

Are we done now?!?

Well, you're done with all the ways a player can exit the game, so you're done for this part of the tutorial at least! :]

Where to Go from Here?

So far, you've made some major improvements to your game: you've reduced your game updates to a reasonable level; retained the smooth animation thanks to the magic of interpolation and extrapolation; and dealt with the normal and abnormal ways players can leave a game.

Here's the completed project for this part of the tutorial.

There are still a number of improvements left to do before you can call your game finished, though:

  • Handling messages arriving out-of-order
  • Running your cross-platform game on Android
  • Running your game with more than two players
  • Discovering a BIG DARK SECRET about multiplayer tutorials :]

So stay tuned! You'll cover these issues (and more!) in the Part 4 of this multiplayer tutorial. As always, if you have comments or questions, feel free to join the discussion below!

Todd Kerpelman

Contributors

Todd Kerpelman

Author

Over 300 content creators. Join our team.