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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Creating a Cross-Platform Multi-Player Game in Unity — Part 3
35 mins
- Reducing Network Traffic
- An Aside on Compression Strategies
- Decreasing Update Frequency
- Trick #1: Interpolation
- Trick #2: Extrapolation
- Finishing a Game
- Sending a Game Over Call
- Receiving the Game Over Call
- Leaving a Game (Normally)
- Leaving the Game (Less Normally)
- Leaving the Game (Abnormally)
- Where to Go from Here?
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.
_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.
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:
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!
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.