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 5 of 6 of this article. Click here to view the first page.

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:

  1. Whether or not this message should be sent reliably
  2. 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 problem with reliable messages

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.

Todd Kerpelman

Contributors

Todd Kerpelman

Author

Over 300 content creators. Join our team.