Building Your First Online Multiplayer Game in Unity – 5 Steps with PlayFab Server Hosting

In this tutorial, you will be shown how to build an online multiplayer game with Unity, using Mirror Networking and PlayFab server hosting. Players must not be on the same local network any more. They will connect to dedicated servers hosted in the cloud, which allows them to play together even though they are a long-distance away from each other.

Architecture Overview

The diagram below shows the architecture we will build.

Players will use their clients and connect to a central server. In our case, we will create a Windows-based Unity application as the game frontend. We will also create a server application in Unity, which will listen on a port for connecting clients. PlayFab hosts this server and provides additional services (for example authentication) that make the development easier.

Implementation Steps

It is important to note that my intention is to provide an “as-simple-as-possible” example, which at the same time covers an end-to-end client-server multiplayer setup using PlayFab and Mirror. You can download the final code from GitHub.

I also noticed that Mirror kept on deprecating functions and restructured classes. I used Mirror 26.2.2 for this implementation.

I broke down the implementation process into five steps. The brown lines on the diagram below show PlayFab API relevant code, while the rest is Mirror related. The mixture of these two makes this exercise tricky at first. I recommend learning the basics of Unity and Mirror (maybe by first implementing a local multiplayer game, as I described in my earlier post).

The detailed steps are explained in the next chapters. To summarize it, you need a simple game with some physics. Enable PlayFab in Unity, activate the server by initializing PlayFab, and start a Mirror server. The clients then log in and request the servers and server’s address to be able to connect to it. Finally, the clients send their PlayFab ID to PlayFab.

Prepare Your Project

On your PlayFab Dashboard, choose your title, then click on Multiplayer, and enable it. PlayFab will ask for your credit card, but Microsoft allows free access, as long as you don’t use it extensively. Always be aware of the conditions that allow you to have free services so you won’t have to pay. I found quite generous 750 Av2 free hours per month.

Now go to your Unity Editor and carry out the following steps:

  • Create your PlayFab account and allow your project in Unity to use it. You can follow my earlier post how to go about it.
  • Add the environment variable ENABLE_PLAYFABSERVER_API to your project (Unity Editor > File > Build Settings… > Player Settings… > Player > Other Settings > Scripting Define Symbols).
  • Download PlayFab Game Server SDK (GSDK) from here. Start (or drag-and-drop into your project) PlayFabMultiplayerSDK.unitypackage, which will import the GSDK packages to Unity.

Let’s implement both the server and the client side in one project!

  1. Create two empty GameObjects, for example “PlayFabServer” and “PlayFabClient”.
  2. Create two scripts, “PlayFabServer.cs” and “PlayFabClient.cs”. Inherit both of them from NetworkManager.
  3. Add these scripts (components) to the respective GameObjects.
  4. Remove the auto-generated Kcp Transport, and add Telepathy Transport to both client and server.

Kcp is the newly recommended transport for Mirror, and so Mirror deprecated the Telepathy Transport in the latest release. However, I found that Docker has trouble in exposing ports with the Kcp transport protocol (I also tried calling it UDP, without success). So I just use TCP for now, let me know if it works with Kcp for you, because that claimed to be faster and more suitable for game communications.

(1) Initialize Your Server

I suggest building a local test environment to run your server, instead of uploading it to PlayFab right away. This step is not mandatory, but if your API calls in the server are not 100% as required by PlayFab, PlayFab will not run it, and reasons won’t be stated as to why it wasn’t run by PlayFab. So, first, make sure that your server is working in the local environment, and only then should you upload it to PlayFab.

Building a Local Test Environment

Fortunately, PlayFab provides all you need for the test environment, but configuring it is quite tricky (saying hard would depreciate the fun of exploration). Here are the steps you should follow (again, check the official documentation as well):

  1. You need to install Docker itself, your server will run in a container.
  2. Ensure Docker uses Windows containers (right-click on Docker icon, Switch to Windows containers…).
  3. Download MockVMAgent from here, and extract it.
  4. Start PowerShell as Administrator.
  5. Copy the following into PowerShell, so that you can execute the setup script in the next step:
    Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
  6. Navigate to the MockVMAgent folder, and execute the Setup.ps1 file.
    This will set up your appropriate docker network and needed firewall rules.
  7. Temporarily turn off your public Windows Firewall, because it prevents the server from reaching or accessing the mock PlayFab agent.

The MultiplayerSettings.json file under your MockVMAgent folder is the key configuration file, which is actually your build configuration. It is critical to have this file configured correctly and understand the function of each setting.

RunContainer: set this value to true, so that the MockVmAgent can create and run a container. Your server application will run in this container.

OutputFolder: after starting the MockVmAgent, it will create this folder, and store important log files. Because of JSON, always use double slashes such as “C:\\MyGame\\”.

NumHeartBeatsFor(Activate/Terminate)Response: set the time (number of heartbeats) to wait until changing between the states. I found useful to set the NumHeartBeatsForTerminateResponse to a high value, otherwise, it will terminate your server too early, and the MockVmAgent will have to be restarted.

AgentListeningPort: this is the port for the Mock Agent (do not confuse it with the game port!). It is important that your server can access this port.

AssetDetails: the paths inside of your container.

PortMappingsList: the NodePort is the exposed port, where the clients should connect to. Docker will map this port to the GamePort. The GamePort is where your Unity Mirror server is listening. So when a client connects to the NodePort, they are actually connecting to the GamePort.

ContainerStartParameters: as we run it in a container, the StartGameCommand describes the path to the Unity server executable, and the command to start it.

ImageDetails: Microsoft provides a standard image for PlayFab containers, leave it as it is, Docker will download it and start a container when you execute the MockVmAgent.

TitleId: it is the Id (not the name!) of your game, PlayFab generates it when you first create your title within your Studio. You can find it at “My Studios and Titles”, click on the gear next to your title to see it. For the mock, you can set whatever value you want, later the client will need it.

BuildId: specific for one multiplayer build, with its own build configuration (where you set up the used VMs, ports, etc.). Similiar to the TitleId, the mock will generate one if you leave it default and show you when you start the MockVmAgent. The BuildId is necessary to get the specific server’s IP and port on PlayFab, but in case of the mock, you will use localhost and the NodePort, so leave the BuildId as it is.

SessionId: clients which play with each other belong to one session, which is identified by the SessionId.

The original architecture will change a bit, as we will run the whole server infrastructure on our machine. Basically, this is what we want to build (you can align your port numbers if there is port conflict):

Starting PlayFab API

Finally, go to Unity Editor / Visual Studio and write the code for PlayFab API calls. At least, for your server, you need to create a new script (PlayFabServer.cs), inherit the class from Mirror’s NetworkManager, add it to a GameObject, and with the Start() method execute the PlayFab API calls:

void StartPlayFabAPI() { PlayFabMultiplayerAgentAPI.Start(); PlayFabMultiplayerAgentAPI.OnServerActiveCallback += OnServerActive; PlayFabMultiplayerAgentAPI.OnMaintenanceCallback += OnMaintenance; PlayFabMultiplayerAgentAPI.OnShutDownCallback += OnShutdown; PlayFabMultiplayerAgentAPI.OnAgentErrorCallback += OnAgentError; StartCoroutine(ReadyForPlayers()); } IEnumerator ReadyForPlayers() { yield return new WaitForSeconds(.5f); PlayFabMultiplayerAgentAPI.ReadyForPlayers(); }
Code language: JavaScript (javascript)

Creating a Server Build

To build the game server, go in Unity Editor to File > Build Settings…, set the Target Platform to Windows, the Architecture to x86_64, and check the Server Build checkbox. Click on Build, and select the folder where to create the server build.

Afterwards, go to the server build folder, and zip all the files in it. Then update the MultiplayerSettings.json’s LocalFilePath to point to your zip file, and StartGameCommand to point to your server’s executable, which will start automatically when the container starts up.

Run The Server in The Local Test Environment for The First Time

Now you are ready to start the MockVmAgent.exe, and see your game server running in the container.

You can find a detailed state transition diagram in the official documentation.Your game server will basically go into “Initializing” after the Start() command, then proceed into “StandingBy” when we call ReadyForPlayers(), and goes to “Active” when the client calls RequestMultiplayerServer() (see later at step 3). When the container process exists (see NumHeartBeatsForTerminateResponse in build config), the state of the game server goes into “Terminating” and PlayFab stores it as archived session.

Starting Mirror Server

After you initialized PlayFab with the API calls, execute Mirror’s StartServer(), which will listen on port 7777 (by default) for incoming client connections.

(2) Authenticate The Client

Basically, all of your players with their game clients need to connect to a listening game server. With Mirror you can implement both the client and the server side, but you will need PlayFab if the clients and the server are not on the local network.

In this case, you deploy the game server into PlayFab, but all the clients require the server’s address and port. PlayFab provides a method for the clients to request this information, however, it requires the client to login first. This authentication step is just exactly what I described in my earlier post.

Recap the key idea quickly:

var request = new LoginWithCustomIDRequest { PlayFabId, CreateAccount = true }; request.TitleId = "YourTitleId"; PlayFabClientAPI.LoginWithCustomID(request, OnLoginSuccess, OnLoginFailure); private void OnLoginSuccess(LoginResult obj) { RequestMultiplayerServer(); }
Code language: JavaScript (javascript)

If the login was successful (OnLoginSuccess), you can now request for the server’s IP and port.

(3) Requesting Server

If you deploy your server in a local container with the help of the MockVMAgent (as described in Step 1), you can skip the requesting server address. Why?

Because your server is on “localhost”, and the port number is what you specified in MultiplayerSettings.json’s NodePort property. You only need to set the proper address and port for the Mirror components and start the client.

public class PlayFabClient : NetworkManager { private void RequestMultiplayerServer() { this.networkAddress = "localhost"; this.GetComponent<TelepathyTransport>().port = 56100; this.StartClient(); } }
Code language: HTML, XML (xml)

If you are the lucky one, it will start working right away, congratulations! It is important to get this running on your local environment with the MockVMAgent before we can move on to creating a build on PlayFab and upload our game server. At the end of this article, I will show you ways to troubleshoot typical problems, but now move on to try it on PlayFab.

Install The Server To PlayFab

After thoroughly testing your server locally, you can move on and install it on PlayFab. The prerequisite of this step is that you have a “Build” in PlayFab. For that go to PlayFab > Multiplayer > Builds > New Build.

Fill the form as on the screenshots below. Some important hints:

  • If you are just experimenting, choose a virtual machine that is free for some time.
  • Choose Windows as operating system.
  • Upload your earlier zipped file, but do not use the JSON notation for the folders any more (e.g. C:\\Assets).
  • The port is your GamePort, where the Mirror server is listening (not the NodePort!).
  • Set at least one StandBy server as this will allow the clients to login. Otherwise, it is just an “empty” build. Also, set the maximum at least to one.

After clicking Save, PlayFab deploys the server. It can take several minutes until the status changes to “Deployed”:

Allow Clients to Start Games

During development, allow clients to start the game, in other words, allow the client with a new SessionId turn the StandingBy server to Active by calling the RequestMultiplayerServer function.

For that, go to the gear next to your title > Title settings > API Features > check Allow client to start games > Save. Note: this change will be effective only after some time.

Request Server For The Clients

After you have authenticated your client successfully, write the following code to request server information:

private void RequestMultiplayerServer() { RequestMultiplayerServerRequest requestData = new RequestMultiplayerServerRequest(); requestData.BuildId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; requestData.PreferredRegions = new List<string>() { "NorthEurope" }; requestData.SessionId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; // requestData.SessionId = System.Guid.NewGuid().ToString(); PlayFabMultiplayerAPI.RequestMultiplayerServer(requestData, OnRequestMultiplayerServer, OnRequestMultiplayerServerError); } private void OnRequestMultiplayerServer(RequestMultiplayerServerResponse response) { this.networkAddress = response.IPV4Address; this.GetComponent<TelepathyTransport>().port = (ushort)response.Ports[0].Num; this.StartClient(); }
Code language: JavaScript (javascript)

You can find the BuildId in PlayFab at Multiplayer > Builds under your build name. If you start a new session with your client, you can generate a new session with a new SessionId. After the client connects to the server, you will find the generated SessionId in PlayFab at Multiplayer > Servers.

You will also notice that the server state changed from “StandingBy” to “Active”. Only those clients with related SessionId can join to the active server.

Also, note that if you allow only one StandingBy server, and you maximized the number of servers to one, you cannot connect with another client with new SessionId, because PlayFab has already occupied the only one server available.

The following diagram shows the states of the game servers in PlayFab and their triggers for moving between these states:

Terminating The Game Server

Your server will be in Active state permanently if you don’t call “Application.Quit()”. As calling this method will shut down the game server process, and your server will transit into a Terminated state. Afterward, PlayFab provides a new StandingBy server for new sessions.

It means you have to start up and activate the server, and also terminate it at the right time. PlayFab suggests stopping the server (Application.Quit()) when all the clients have disconnected.

If your request for multiplayer server is successful, PlayFab will provide the network address and the port for the Mirror client.

4) Client-Server Connection

On the client side, the StartClient() method will try to connect to the game server already deployed to PlayFab. Always check the system callbacks on both server and client to know if the connection was successful. For example:

  • OnClientConnect: this is called on the client when connected to the server.
  • OnClientDisconnect: this is called on the client when disconnected from the server.
  • OnServerConnect: this is called on the server when a client connects.
  • OnServerDisconnect: this is called on the server when a client disconnects.

If the client connects to the server (OnClientConnect), you can move on to the next step, and send your PlayFabId from the client to the server.

5) Add The New Player To PlayFab

Players will join and leave game sessions. Here we want to achieve two goals:

  • Let PlayFab know about the actual number of connected users.
  • Terminate the server if there is no client using the server anymore.

At this point we need to “bind” PlayFab with Mirror. PlayFab works with PlayFabId, Mirror with NetworkConnections. Mirror will note if a client disconnects (by the OnServerDisconnect method), but how will it know the player (PlayFabId) that left?

To solve this, I packaged these two information into “PlayerInfo” and let client and server together fill out the fields.

public struct PlayerInfo : NetworkMessage { public string PlayFabId; public int ConnectionId; }
Code language: PHP (php)

The following diagram shows the flow how clients and the server set the PlayerInfo containing both the PlayFabId and the ConnectionId:

After authentication, the client receives the PlayFabId. After connecting successfully, the OnServerConnect will be called on the server. Put the ConnectionId into the PlayerInfo, and send it back to the client. The client then sends it back to the server, including the PlayFabId as well. Now the server has every information to update the connected player’s list via the PlayFab API.

If a client disconnects, the server notices it (OnServerDisconnect), then it searches and identifies which client (ConnectionId) left, and finds the related PlayFabId as well. Then the server informs PlayFab about the new list of connected players. Here you can also add the condition to stop the server if there are no clients connected to it.

public override void OnServerDisconnect(NetworkConnection conn) { base.OnServerDisconnect(conn); var playerConnection = playerConnections.Find(c => c.ConnectionId == conn.connectionId); connectedPlayers.Remove(playerConnection.ConnectedPlayer); playerConnections.Remove(playerConnection); PlayFabMultiplayerAgentAPI.UpdateConnectedPlayers(connectedPlayers); if (playerConnections.Count == 0) { StartCoroutine(Shutdown()); }}
Code language: JavaScript (javascript)

Problems and Solutions

Finally, I would like to give some tips on how to troubleshoot upcoming issues.

Port Already Exists When Starting MockVMAgent

When you are trying to start the MockVMAgent you will see:

Unhandled exception. Docker.DotNet.DockerApiException: Docker API responded with status code=InternalServerError, response={“message”:”failed to create endpoint stupefied_lewin on network playfab: failed during hnsCallRawResponse: hnsCall failed in Win32: The specified port already exists. (0x803b0013)”}

This happens because the server was not terminated, and the container is still running. Run docker ps, and then docker kill <containerid>, to remove the old container.

MockVMAgent Is Stuck in Initializing State

When the MockVMAgent is started, it gets stuck in “Initializing” state, and writes to the console:

Operation: Continue, Maintenance:, State: Initializing

It will only continue to the next state, when you call “PlayFabMultiplayerAgentAPI.ReadyForPlayers();” from the server.

Server Cannot Connect To The VMAgent

When the MockVMAgent is started, it will keep on giving error message indicating that the VMAgent is not reachable:

Curl error 7: Failed to connect to 172.19.0.11 port 56001: Timed out CurrentError: Cannot connect to destination host

Be aware that when you start the MockVMAgent, it will start a container where your server will run, and a PlayFab VM Agent mock, which runs out of your container. Still, this agent should be reachable from the container.

Firstly, you can check if you can “curl” out from the container and reach your local machine. By default, your gateway address (ipconfig) in the container is your localhost.

I found that Windows Public Firewall blocks the container to reach the agent on the localhost, so if you turn the firewall off, you can have a quick fix on this problem.

Setup Of MockVMAgent Fails

In PowerShell, when you are trying to start Setup.ps1, you get the following error:

.\Setup.ps1 : File C:\MockVmAgent\Setup.ps1 cannot be loaded because running scripts is disabled on this system. For
more information, see about_Execution_Policies at https:/go.microsoft.com/fwlink/?LinkID=135170.

You have to change the execution policy in PowerShell by executing the following command:

Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process

Another error you may notice when trying to start Setup.ps1 in PowerShell:

Start-Service : Service ‘Docker Engine (docker)’ cannot be started due to the following error: Cannot open docker
service on computer ‘.’.

You need to start PowerShell as Administrator. If you notice the following error message when starting the Setup.ps1:

Unhandled exception. Docker.DotNet.DockerApiException: Docker API responded with status code=InternalServerError, response={“message”:”failed to create endpoint nice_engelbart on network playfab: failed during hnsCallRawResponse: hnsCall failed in Win32: The process cannot access the file because it is being used by another process. (0x20)”}

Execute “docker network prune”, and start again Setup.ps1.

Starting Of The Server in Unity Fails

When you clieck on the Play button in Unity Editor, you will see the following message:

Environment variable GSDK_CONFIG_FILE not defined

This is normal. This file will be automatically generated by the MockVMAgent or PlayFab. You just need to create a Server Build as described in Step 1.

Client Does Not Connect to the Server in the Local Test Environment

If the client does not want to connect to the server in your local Docker container, you can check the following.

Use the tool portqry to check if the server is listening on the NodePort of the container. I found that Docker has problem with KCP, so I set in Unity instead of KCP the Telepathy Transform. In this way, Docker can forward the GamePort to NodePort as TCP, and the client can connect.

Summary

At first using PlayFab from Unity can be challenging. I think an end-to-end description of how to achieve it is not available yet. With this guide, I wanted to fill this gap. Enjoy your Unity/Mirror based multiplayer game hosted on PlayFab.

3 thoughts on “Building Your First Online Multiplayer Game in Unity – 5 Steps with PlayFab Server Hosting”

  1. Thanks for writing this guide! I’m attempting to follow it (July 21, 2021), but I’m hitting several snags.

    I downloaded the project from github and noticed there are several errors when I open it in Unity. The Start() methods in both PlayFabClient.cs and PlayFabServer.cs each obscure the Start() method of the NetworkManager they inherit from. Should we use the “new” keyword or the “override” keyword and run the base NetworkManager Start() script?

    I’m also attempting to follow your setup for a test environment on my computer, but I have Windows 10 home and it seems like you need Pro or Enterprise to enable Windows Containers with Docker. Is there any workaround or alternative to this?

    Thanks in advance!

    1. Hello Trevor,
      You can just add override keyword to the Start() to avoid the warning. We have already the opportunity to use Linux containers with both LocalMultiplayerAgent and Thundernetes.

  2. Hey, thanks for such a detailed post.

    I’m exploring the implementation of having Server API and Client exist in the same project because that’s just easier than managing two separate projects as Playfab recommends.. any thoughts on this?

    I want things to be convenient to develop but at the same time leaking that developer key is a huge concern. would #if checks be secure enough, and if so where would I need them? Building a classical MOBA for context, haven’t made any server API calls yet but I’d imagine that will happen around the time matchmaking enters the scene.

    How would I mitigate this risk?

Leave a Comment

Your email address will not be published. Required fields are marked *