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

Starting the Multiplayer Game

Now that you've cleaned up your code, you can add some multiplayer game logic to your project.

Add the following code to the if (success) block of OnRoomConnected in MultiplayerController.cs:

lobbyListener.HideLobby();
lobbyListener = null;
Application.LoadLevel("MainGame");

This hides the lobby dialog, clears out your mainMenuScript and then loads up your MainGame scene. If you were to run your app at this point, you'd see that you're taken into the game as soon as you're connected.

Having only one car on the screen somewhat limits the multiplayer experience. Your next task is to add the opponent's car.

Browse out your assets folder; you'll see that there's an existing Prefab for an OpponentCar object. Click on it, then click Add Component and add a new Script Component. Name it OpponentCarController, then click Create and Add.Opponent Car

Finally, double-click on the newly created script to edit it. OpponentCarController will represent the other players in the game; it doesn't need to be very smart, since your human opponents will do all the driving.

At the top of your class definition, add the following variable:

public Sprite[] carSprites;

This variable is going to hold the sprites of various car images.

Next, add the following method into the script:

public void SetCarNumber (int carNum) {
    GetComponent<SpriteRenderer>().sprite = carSprites[carNum-1];
}

This lets you store your collection of car sprites in an array (yellow, blue, and red); you can then set the car sprite for a specific player simply by calling SetCarNumber on it.

Go back to Unity, then click on the OpponentCar Prefab. Find the Car Sprites entry in the script component, and change its size to 3. Then select car_1 for element 0, car_2 for element 1, and car_3 for element 2 as shown below:

Adding car sprites

Assigning Player Colors

At this point, you're left with an interesting problem: how do you determine the color to assign to each player? If it was simply the player's choice, all players could choose the yellow car. Or one player could think her car is red and her opponent's car is blue, while her opponent thinks the exact opposite. How can you get all game clients to agree on the state of the world?

At first, you might think "Who cares?" But it quickly becomes evident that this sort of thing does matter in many cases. In a poker game, all clients need to decide who should deal first. In a real-time strategy game, you need to decide who should start at which base.

In your racing game, you need to decide which lane each client will start in; therefore this problem applies to you. Other game tutorials on this site usually solve this problem by generating random numbers; the highest number goes first, deals first, gets the yellow car and so on.

Shouting Random Numbers

Instead of this approach, you can take advantage of the participant ID entity in Google Play Games.

Each player is assigned a participant ID by the game service when they join a game. This participant ID is different than a traditional player ID as it is randomly generated and only exists for the duration of that gameplay session. It's a nice way for strangers to refer to each other in a game without having to use any data that could be traced back to the actual player.

Participant IDs

You can easily sort out the game configuration by sorting the list of participant IDs and assigning qualities to players based on their order in this list; the yellow car goes to the first listed participant, the blue car to the second, etc. In a poker game, the player listed first could be the dealer, and so on.

Note: Google Play engineers have asked me to point out that these participant IDs are only mostly random; it's possible for one player to get a lower-ordered participant ID more often than other players.

If your game design confers a major gameplay advantage to the player listed first, you might want to sort the list of participant IDs, then randomly choose the player who gets to be dealer, gets the sniper rifle, or gets the inside lane. In your case, the yellow car isn't all that special, so you don't need to do anything beyond sorting the list.

Note: Google Play engineers have asked me to point out that these participant IDs are only mostly random; it's possible for one player to get a lower-ordered participant ID more often than other players.

If your game design confers a major gameplay advantage to the player listed first, you might want to sort the list of participant IDs, then randomly choose the player who gets to be dealer, gets the sniper rifle, or gets the inside lane. In your case, the yellow car isn't all that special, so you don't need to do anything beyond sorting the list.

Open MultiplayerController.cs and add the following line to the top of the file:

using System.Collections.Generic;

This ensures that List is defined.

Next, add the following method:

public List<Participant> GetAllPlayers() {
    return PlayGamesPlatform.Instance.RealTime.GetConnectedParticipants ();
}

This simply gets a list of all participants in the room. It's worth noting here that the library already sorts this list for you by participantId, so you don't need to sort it yourself.

Add the following method to get the ParticipantID of the local player:

public string GetMyParticipantId() {
    return PlayGamesPlatform.Instance.RealTime.GetSelf().ParticipantId;
}

Open GameController.cs and add the following imports:

using System.Collections.Generic;
using GooglePlayGames.BasicApi.Multiplayer;

Now add the following variables near the top of the class definition:

public GameObject opponentPrefab;

private bool _multiplayerReady;
private string _myParticipantId;
private Vector2 _startingPoint = new Vector2(0.09675431f, -1.752321f);
private float _startingPointYOffset = 0.2f;
private Dictionary<string, OpponentCarController> _opponentScripts;

You'll be using all these variables in the next method. Add the following code to the empty implementation of SetupMultiplayerGame():

// 1
_myParticipantId = MultiplayerController.Instance.GetMyParticipantId();
// 2
List<Participant> allPlayers = MultiplayerController.Instance.GetAllPlayers();
_opponentScripts = new Dictionary<string, OpponentCarController>(allPlayers.Count - 1); 
for (int i =0; i < allPlayers.Count; i++) {
    string nextParticipantId = allPlayers[i].ParticipantId;
    Debug.Log("Setting up car for " + nextParticipantId);
    // 3
    Vector3 carStartPoint = new Vector3(_startingPoint.x, _startingPoint.y + (i * _startingPointYOffset), 0);
    if (nextParticipantId == _myParticipantId) {
        // 4
        myCar.GetComponent<CarController> ().SetCarChoice(i + 1, true);
        myCar.transform.position = carStartPoint;
    } else {
        // 5
        GameObject opponentCar = (Instantiate(opponentPrefab, carStartPoint, Quaternion.identity) as GameObject);
        OpponentCarController opponentScript = opponentCar.GetComponent<OpponentCarController>();
        opponentScript.SetCarNumber(i+1);
        // 6
        _opponentScripts[nextParticipantId] = opponentScript;
    }
}
// 7
_lapsRemaining = 3;
_timePlayed = 0; 
guiObject.SetLaps(_lapsRemaining);
guiObject.SetTime(_timePlayed);
 _multiplayerReady = true;

Taking each numbered comment in turn:

  1. This gets the local player's participant ID and stores it as a local variable.
  2. This grabs the list of sorted participants from the multiplayer controller so you can iterate through them.
  3. This calculates the start point for each car based on its order in the list.
  4. If the participantID of the current player matches the local player, then instruct the CarController script to set the car number, which determines its color.
  5. Otherwise, instantiate a new opponent car from the Prefab object and set its car number and color as well.
  6. You're storing this carController in a dictionary with the participant ID as the key. Since you'll be getting a lot of messages in the form "Participant ID xxx said so-and-so", storing your opponents in a dictionary is an easy way to refer to them later by their participant ID.
  7. Finally, you initialize a few other game constants and set _multiplayerReady to true, signalling that you're ready to receive multiplayer message. Since there's no guarantee all clients will start at precisely the same time, weird things may happen if you begin to receive game messages while you're still setting up. The _multiplayerReady flag will protect against that, as you'll see later.

You might be thinking "Hey, isn't it overkill to create a dictionary to store all of my OpponentCarControllers, when there's really only one? Why not just have a simple opponentCar variable and be done with it?" Although you're only testing the game with two players at the moment, you'll want the model to be flexible enough to handle more than two players in the future, and the dictionary is a good way to prepare for that scenario.

Go back to Unity and open the MainGame scene. Select GameManager and set the OpponentPrefab variable in the inspector to be your OpponentCar prefab object, then save the scene as shown below:

Setting the Opponent Car

Build and run your game again; start a multiplayer game and you'll see that one player has been assigned the yellow car and the other has been assigned the blue — and each player can drive themselves around the track.

Screen Shot 2015-01-22 at 6.30.10 PM

Drive each of the cars around the track, and you'll notice some strange gameplay: your car's progress isn't reported on your opponent's device and vice versa. Time to get these devices talking to each other!

Todd Kerpelman

Contributors

Todd Kerpelman

Author

Over 300 content creators. Join our team.