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

In the second part of this tutorial, you’ll write the code to have users connect with each other and then get a race started! By Todd Kerpelman.

Leave a rating/review
Save for later
Share

Up to this point, you’ve spent a lot of time adding frameworks and filling out forms to get to the very first step of any multiplayer game — signing in and out. Now you can move on to the real work of making the game happen.

In this tutorial part, you are going to learn the following:

  • Some general game networking theory
  • How to to connect Google Play Services
  • How to create a game app using Google Development console.
  • The code necessary to have devices talk with each other.
  • Mad driving skills! :]

You can download the completed version for part one over here. You can read the first part over here.

Note: If you are downloading the starter project for this part, you will need to configure the ClientID, or the project will not work. To learn how to obtain a ClientID, check out the first part of this tutorial series.

An Introduction to Matchmaking

Matchmaking is one of the main functions of Google Play game services and other multiplayer frameworks. When your app tells Google Play’s servers that the player is interested in a multiplayer game, Google Play then looks for other people in the world who are also interested in playing that same game at that same time.

Forever looking for others

Matchmaking is a somewhat complex operation. the Google Play servers don’t just pick the next person they find; if one player is in Helsinki and the other player is in Sydney, the network connection between them would be terribly laggy. Unfortunately, even the best network programmers haven’t yet found a way to increase the speed of light. :]

So Google Play may choose to wait for better options to come up for our Helsinki player and only match them against the player in Sydney if no better option comes along. Fortunately, you don’t have to worry about any of that logic; you just need to know that the underlying service does a pretty good job at connecting people within a reasonable amount of time.

Ping time

Many multiplayer games, such as MMOs, require that all clients connect to a server in a traditional client-server model like the one illustrated below:

A traditional client-server setup

However, many other games, like yours, will pair up players then connect their devices to each other through a peer-to-peer mesh network, where all players’ devices talk directly to each other as shown below:

Peer to peer mesh network

Invites, or Auto-Match?

Traditionally, there are two ways that players are matched with opponents:

  1. Players can request to be matched up with specific friends.
  2. Players can ask to be auto-matched, which tells Google Play to find other people looking to play this game.

I find that vast majority of games use auto-matching; players tend to be impatient and just want to start a game instead of waiting around for their friend to see the invitation notification and decide whether or not to accept it.

Tired of waiting

Invitations typically happen when two players are in the same physical location and spontaneously decide to play a game, or they’ve scheduled a gameplay session ahead of time. In most other cases, players tend to stick with auto-matching.

Note: Turn-based games are an entirely different story, as they aren’t as time-sensitive and players happily invite their friends — since they don’t have to wait around for them! :]

For these reasons, you’ll implement auto-matching in your game.

Adding Auto Matching

Open your Circuit Racer project in Unity, then open up your MultiplayerController script from the Scripts folder.

Create the following private instance variables in your class:

private uint minimumOpponents = 1;
private uint maximumOpponents = 1;
private uint gameVariation = 0;
  1. minimumOpponents is the minimum number of opponents to match your player against. You’ll create a two-player game for now, so set this to 1.
  2. maximumOpponents is the maximum number of opponents to match your player against. Since it’s a two-player game, set this to 1 as well.
  3. gameVariation specifies which particular multiplayer variation of your game that you wish to play. If Circuit Racer had a racing mode and a destruction derby mode, you wouldn’t want the racing players to end up auto-matched with people playing in the destruction derby!

The variables are all unsigned integers, meaning that they cannot hold a negative value. After all, you wouldn’t to play a game with negative players. Would that mean that you would cease to exist? :]

I just want to race

You could specify a variation of 1 for racing players, and a variation of 2 for destruction derby players to keep them separated. Using too many variants of your game segments the players and means there’s fewer players in each pool. In this tutorial, you only have the one variant, so use 0 as the default.

Next, add the following method to your MultiplayerController class: (MonoDevelop will complain that your argument is invalid because you’ve declared that MultiplayerController will be your RealTimeMultiplayerListener, and you haven’t set it up that way — but that’s an easy fix.)

private void StartMatchMaking() {
    PlayGamesPlatform.Instance.RealTime.CreateQuickGame (minimumOpponents, maximumOpponents, gameVariation, this);
}

You will notice that you’ve passed in all your private instance values. The final argument is the RealTimeMultiplayerListener instance that receives any messages from the Google Play games service about the status of your multiplayer game. To keep all multiplayer logic contained in the same class, you use this to indicate that the MultiplayerController class is your listener.

Now fix the compile error by changing the following line at the top of your class:

public class MultiplayerController  {

…to the following

public class MultiplayerController : RealTimeMultiplayerListener {

Here you declare that MultiplayerController conforms to the RealTimeMultiplayerListener interface, which is a list of methods a class promises to implement; this is very similar to a protocol in Objective-C.

Look at the SignInAndStartMPGame(); you’ll see there are two places in the code where the comments say you can start a multiplayer game like so:

// We could start our game now

Replace those comments with the following method call:

StartMatchMaking();

Head back to Unity, and you’ll see…a bunch of new errors!

So Many Unity Errors

Unity is complaining that you aren’t conforming to the interface as you promised. The RealTimeMultiplayerListener interface says any conforming class must implement the OnRoomConnected(bool success) method, among others.

To get rid of the errors, you can create a few mostly stubbed-out methods for the time being.

Generating Code

You’ll replace MonoDevelop’s boilerplate exception handling code with something that just prints out simple debug messages for now.

Note: Here’s a handy shortcut to generate stub methods in MonoDevelop: right-click on a blank line in your file and select Show Code Generation Window. Select Implement Interface Methods from the resulting dialog, check all the methods that appear, hit Return, and MonoDevelop automatically adds all the selected stub methods for you!

First, add the following utility method in MultiplayerController that prints out status messages from your matchmaking service:

private void ShowMPStatus(string message) {
    Debug.Log(message);
}

Now you can tackle each interface method in turn.

Add the following method, or alternately replace the contents of your stub method if you auto-generated it in the previous step:

public void OnRoomSetupProgress (float percent)
{
    ShowMPStatus ("We are " + percent + "% done with setup");
}

OnRoomSetupProgress indicates the progress of setting up your room. Admittedly, it’s pretty crude; on iOS in particular, I think it jumps from 20% to 100%. But hey, it’s better than nothing! :]

Some of you may be wondering what is a “room”? In Google Games terminology, a room is a virtual place that players gather to play real time games.

Add the following method, or replace the contents of your stub method:

public void OnRoomConnected (bool success)
{
    if (success) {
        ShowMPStatus ("We are connected to the room! I would probably start our game now.");
    } else {
        ShowMPStatus ("Uh-oh. Encountered some error connecting to the room.");
    }
}

OnRoomConnected executes with success set to true when you’ve successfully connected to the room. This would normally be the point where you’d switch to a multiplayer game.

Add or replace the following method:

public void OnLeftRoom ()
{
    ShowMPStatus ("We have left the room. We should probably perform some clean-up tasks.");
}

OnLeftRoom tells you that your player has successfully exited a multiplayer room.

Add or replace the following method:

public void OnPeersConnected (string[] participantIds)
{
    foreach (string participantID in participantIds) {
        ShowMPStatus ("Player " + participantID + " has joined.");
    }
}

You will receive an OnPeersConnected message whenever one or more players joins the room to which your local player is currently connected. You’ll learn more about participantIds later, but for now all you need to know is that they’re unique IDs for a specific player in this gameplay session.

Add or replace the following method:

public void OnPeersDisconnected (string[] participantIds)
{
    foreach (string participantID in participantIds) {
        ShowMPStatus ("Player " + participantID + " has left.");
    }
}

OnPeersDisconnected is similar to OnPeersConnected but it signals that one or more players have left the room.

Now for the last interface method! Add or replace the following method:

public void OnRealTimeMessageReceived (bool isReliable, string senderId, byte[] data)
{
    ShowMPStatus ("We have received some gameplay messages from participant ID:" + senderId);
}

You call OnRealTimeMessageReceived whenever your game client receives gameplay data from any player in the room; this handles all of your multiplayer traffic.

Once you’ve added all of the above methods, head back to Unity and check that all your compiler errors have resolved. If so, hit Command-B to export and run your project in Xcode. When the game starts, click the Multiplayer button and check the console log, where you should see something similar to the following:

DEBUG: Entering internal callback for RealtimeManager#InternalRealTimeRoomCallback
DEBUG: Entering state: ConnectingState
We are 20% done with setup

That means you’re in a two-player multiplayer lobby, waiting for another player to join. Success! :]

However, you’re the only person on earth who can play this game, so you might be waiting a looong time for someone else to join. Time to fix that.

Todd Kerpelman

Contributors

Todd Kerpelman

Author

Over 300 content creators. Join our team.