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.
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 2
55 mins
- An Introduction to Matchmaking
- Invites, or Auto-Match?
- Adding Auto Matching
- Running on a Second Device
- Running on Android
- Running on iOS
- Adding a Simple Waiting Room UI
- Adhering to the Delegate Pattern
- Starting the Multiplayer Game
- Assigning Player Colors
- Making the Cars Move
- Sending Message Data
- Reliable or Unreliable?
- Receiving Message Data
- Where to Go From Here?
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.
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.
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.
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:
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:
Invites, or Auto-Match?
Traditionally, there are two ways that players are matched with opponents:
- Players can request to be matched up with specific friends.
- 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.
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.
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;
-
minimumOpponents
is the minimum number of opponents to match your player against. You’ll create a two-player game for now, so set this to1
. -
maximumOpponents
is the maximum number of opponents to match your player against. Since it’s a two-player game, set this to1
as well. -
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? :]
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!
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.
You’ll replace MonoDevelop’s boilerplate exception handling code with something that just prints out simple debug messages for now.
You’ll replace MonoDevelop’s boilerplate exception handling code with something that just prints out simple debug messages for now.
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.