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?
Making the Cars Move
You'll move the opponent's cars by reporting the location of each car to all players in the room at frequent intervals. When your game receives an update from another client, you can move the appropriate OppponentCar
to its current location.
Here's the big question: how often are these "frequent intervals"? A typical PC game sends updates about 10-30 times per second. That's a lot! However, mobile device games need to worry about battery life and data usage limits, so you will definitely want to send updates less often than that.
You'll start by sending a network call every Update()
— which is definitely too often, but works as a good starting point — and you can work on optimizing your network calls in Part 3 of this tutorial series.
Sending Message Data
Add the following code to the end of DoMultiplayerUpdate()
in GameController.cs:
MultiplayerController.Instance.SendMyUpdate(myCar.transform.position.x,
myCar.transform.position.y,
myCar.rigidbody2D.velocity,
myCar.transform.rotation.eulerAngles.z);
Here you send all information the other players need to display the local player's car appropriately: their x and y coordinates, z-axis rotaion, and the car's current velocity.
At this point, Unity is probably complaining because you haven't defined SendMyUpdate
. You'll do that in just a moment, once you add a bit of supporting code.
Add the following private variables near the top of your MultiplayerController
class definition:
private byte _protocolVersion = 1;
// Byte + Byte + 2 floats for position + 2 floats for velcocity + 1 float for rotZ
private int _updateMessageLength = 22;
private List<byte> _updateMessage;
You'll see how you use each of these variables shortly.
Add the following code to the beginning of private MultiplayerController()
to create your _updateMessage
list:
_updateMessage = new List<byte>(_updateMessageLength);
Then add SendMyUpdate
to your MultiplayerController
class:
public void SendMyUpdate(float posX, float posY, Vector2 velocity, float rotZ) {
_updateMessage.Clear ();
_updateMessage.Add (_protocolVersion);
_updateMessage.Add ((byte)'U');
_updateMessage.AddRange (System.BitConverter.GetBytes (posX));
_updateMessage.AddRange (System.BitConverter.GetBytes (posY));
_updateMessage.AddRange (System.BitConverter.GetBytes (velocity.x));
_updateMessage.AddRange (System.BitConverter.GetBytes (velocity.y));
_updateMessage.AddRange (System.BitConverter.GetBytes (rotZ));
byte[] messageToSend = _updateMessage.ToArray();
Debug.Log ("Sending my update message " + messageToSend + " to all players in the room");
PlayGamesPlatform.Instance.RealTime.SendMessageToAll (false, messageToSend);
}
First, you put your message in a byteArray
. The easiest, if not terribly efficient, way to do this is to build the message using a List
of bytes. You'll re-use the _updateMessage
variable you created in the previous step, which is set at 22 bytes — just enough for everything you need to send. In Unity, floats are represented as 4 bytes.
The very first byte you add is a single byte to represent the protocol version. You can think of this as the version number of the message itself. You'll see why this is important later in this tutorial.
The next byte is the instruction to send. It's possible for a client to send all sorts of messages to the other players, so it's helpful to first include a character as one of your first bytes to describe what's going to be in the rest of the message. In your case, you're using U to declare that you are sending an Update.
After that, you add the passed-in values of the position, velocity, and rotation of the car. AddRange
is one way to add a number of bytes to a list, and the System.BitConverter.GetBytes()
method reinterprets each of these float values as a series of bytes.
Next, you convert this list into a byteArray
using your list's toArray()
method. Finally, you send this message to all the other players using the Google Play library's SendMessageToAll
method, which takes two arguments:
- Whether or not this message should be sent reliably
- The actual message to send, as a byteArray
The second argument is pretty self-explanatory, but what does it mean to send a message "reliably"?
Reliable or Unreliable?
Typically there are two ways a client can send messages to other clients in a multi-player game.
The unreliable way is via the UDP network protocol. When you send messages over UDP, a small percentage of them don't always make it to the target device. And if they are received, there's no guarantee they'll be received in the order they were sent — especially in your game, since you're sending updates so frequently.
The reliable method uses the TCP network protocol. It guarantees that a message will always be received — eventually — by the target device and, even better, all messages will be received in the order they were sent.
So...why wouldn't you choose the reliable method, you ask? The answer is that all this convenience with reliable network messaging comes at a considerable cost in speed. Reliable messages are significantly slower than unreliable ones, and the amount of slowdown can vary a lot.
Think about it this way: since reliable messages are guaranteed to be received in order, what happens if one message doesn't make it to its target? That's right, all of your other messages will be delayed and not received by the other player until the first one is re-sent and received, which can take a considerable amount of time:
The general rule for most game developers is to use reliable messages only when speed isn't important, such as in the theoretical poker game discussed earlier. In fact, many developers of action games try to not use reliable messages at all, and instead implement some lightweight logic as needed on top of the UDP protocol to add little bits of reliability as needed.
Build and run your game now; join a multiplayer game and you'll see from your debug logs that you're sending dozens of messages per second:
Sending my update message System.Byte[] to all players in the room
Sending my update message System.Byte[] to all players in the room
Sending my update message System.Byte[] to all players in the room
Sending my update message System.Byte[] to all players in the room
There's no need to clutter up the debug log with these messages; remove the call to Debug.Log
message from SendMyUpdate()
to streamline the debuglog.
You know that your client is sending out update messages, but now you need to do something when your game receives those messages from an opponent, like, say, move their car around the track! :] You'll tackle that next.