Unity Matchmaker
This guide will help you create a Unity project using Edgegap's Advanced Matchmaker system to automate deployments and connections between players.
This guide will use the open-source sample project Tanks
, available in the Mirror sample under Assets/Mirror/Examples/Tanks
. The final sample can be found on our GitHub.
Matchmaker setup
Create the components
To begin, you will need to create your 3 Matchmaker components. For this project, you may follow along with our basic component tutorial here.
Keep in mind that the components and YAML configuration created with this tutorial are not meant to be used in production.
Release
Local
If you want to test your Matchmaker in a local environment, simply apply your Kubernetes settings as mentionned at the end of the basic component tutorial. You can then skip ahead to the project setup.
With Edgegap
If you instead want to test the Matchmaker in an online environment with Edgegap, you will need to slightly modify your components from the previous tutorial. You will be using the injected variables that are added during the release instead of some hardcoded values. The changes should be as follows:
Director
...
using System;
namespace director
{
public static class Constant
{
static Constant()
{
OpenMatchMatchFunctionHost = Environment.GetEnvironmentVariable("OM_MMF_HOST");
OpenMatchMatchFunctionPort = Int32.Parse(Environment.GetEnvironmentVariable("OM_MMF_HTTP_PORT"));
OpenMatchBackendService = Environment.GetEnvironmentVariable("OM_BACKEND_HOST") + ':' + Int32.Parse(Environment.GetEnvironmentVariable("OM_BACKEND_HTTP_PORT"));
}
public static readonly string OpenMatchMatchFunctionHost;
public static readonly int OpenMatchMatchFunctionPort;
public static readonly string OpenMatchBackendService;
...
}
...
}
Frontend
...
namespace front_end
{
// All the constants used in the program
public static class Constant
{
static Constant()
{
OpenMatchFrontendService = Environment.GetEnvironmentVariable("OM_FRONTEND_HOST") + ':' + Int32.Parse(Environment.GetEnvironmentVariable("OM_FRONTEND_HTTP_PORT"));
}
public static readonly string OpenMatchFrontendService;
}
...
}
Match Function
...
namespace match_function
{
// All the constants used in the program
public static class Constant
{
static Constant()
{
OpenMatchQueryService = Environment.GetEnvironmentVariable("OM_QUERY_HOST") + ':' + Int32.Parse(Environment.GetEnvironmentVariable("OM_QUERY_HTTP_PORT"));
}
public static readonly string OpenMatchQueryService;
public const string MatchName = "basics-match-function";
}
...
}
Once this is done, upload them to Edgegap, then create and release your Matchmaker on the Edgegap dashboard.
Project setup
Next, we want to modify the base Tank project so that the Matchmaker is being used to connect the players together. For the purposes of this sample, we want our players to choose what mode of match they want to play before creating a Matchmaker ticket, so that the Matchmaker will automatically connect 2 players who chose the same one.
In the project, open the scene located under Assets/Mirror/Examples/Tanks/Scenes
. Disable the Network Manager HUD
component of the NetworkManager
gameObject, then create your own HUD to allow the players to enter a game mode, create and delete a ticket when they are not currently in a match, and exit their current match when they are.
Offline HUD

Online HUD

Then, you will need to create new scripts inside your project to manage what requests are sent to the Matchmaker according to what button the player clicks. Your code for managing the requests themselves should have a similar structure to this:
Functions
public class MatchmakerManager : ScriptableObject
{
//when testing the Matchmaker locally, this value should be http://localhost:51504
//when testing the Matchmaker's online release, you do not need to specify the port at the end of the URL
public const string MATCHMAKER_URL = "<FRONTEND_COMPONENT_URL>";
private readonly HttpClient _httpClient = new();
public KcpTransport transport = (KcpTransport)NetworkManager.singleton.transport;
/// <summary>
/// Get a ticket's data from the Matchmaker
/// </summary>
/// <param name="ticketId">Ticket's ID</param>
/// <returns>Ticket data</returns>
public async Task<Ticket> GetTicket(string ticketId)
{
var response = await _httpClient.GetAsync($"{MATCHMAKER_URL}/v1/tickets/{ticketId}");
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"Error code: {response.StatusCode}");
}
var responseContent = await response.Content.ReadAsStringAsync();
Ticket content = JsonConvert.DeserializeObject<Ticket>(responseContent);
return content;
}
/// <summary>
/// Create a new ticket for the Matchmaker
/// </summary>
/// <param name="modeTag">What game mode the players wants to be in</param>
/// <returns>Ticket data</returns>
public async Task<Ticket> CreateTicket(string modeTag)
{
CreateTicketPayload objectToSerialize = new()
{
mode = modeTag
};
var jsonContent = new StringContent(JsonConvert.SerializeObject(objectToSerialize), Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync($"{MATCHMAKER_URL}/v1/tickets", jsonContent);
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"Error code: {response.StatusCode}");
}
var responseContent = await response.Content.ReadAsStringAsync();
Ticket content = JsonConvert.DeserializeObject<Ticket>(responseContent);
return content;
}
/// <summary>
/// Delete a ticket from the Matchmaker
/// </summary>
/// <param name="ticketId">Ticket's ID</param>
public async Task DeleteTicket(string ticketId)
{
var response = await _httpClient.DeleteAsync($"{MATCHMAKER_URL}/v1/tickets/{ticketId}");
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"Error code: {response.StatusCode}");
}
}
/// <summary>
/// Connect the player to a match
/// </summary>
/// <param name="assignment">Ticket's assignment data</param>
public void ConnectPlayer(Assignment assignment)
{
string[] networkComponents = assignment.connection.Split(':');
NetworkManager.singleton.networkAddress = networkComponents[0];
if (ushort.TryParse(networkComponents[1], out ushort port))
{
transport.port = port;
}
else
{
throw new Exception("port couldn't be parsed");
}
NetworkManager.singleton.StartClient();
}
/// <summary>
/// Disconnect the player from a match
/// </summary>
public void DisconnectPlayer()
{
NetworkManager.singleton.StopClient();
}
}
Classes
public class CreateTicketPayload
{
public string mode { get; set; }
}
public class Ticket
{
public string id { get; set; }
public Assignment assignment { get; set; }
public SearchFields search_fields { get; set; }
public Dictionary<string, Extension> extensions { get; set; }
public Dictionary<string, Extension> persistent_field { get; set; }
public string create_time { get; set; }
}
public class SearchFields
{
public string[] tags { get; set; }
}
public class Extension
{
public string @type { get; set; }
public byte[] value { get; set; }
}
public class Assignment
{
public string connection { get; set; }
public Dictionary<string, Extension> extensions { get; set; }
}
However you choose to manage your HUD, make sure to create your MatchmakerManager
instance in your Start()
function to ensure the code works properly.
Build the game server & Containerizing
Once you have finished adding the modifications from the previous step to the project, you will need to build your game server and containerize it. First, make sure the NetworkManager
gameObject has the following settings:
Enable
Auto Start Server Build
in theNetworkManager
component;Set the
port
of theKcpTransport
component to the same value you used for your Matchmaker'sGameServerPort
in the Director component.

Once this is done, head to the Build
screen of the Unity Editor, under File -> Build Settings
in the top menus. Make sure to select the right presets depending on your version of Unity.
Prior to version 2021.2:
Set
Target Platform
toLinux
;Set
Architecture
tox86_64
;Check the
Server Build
option.
Otherwise:
Set
Platform
toDedicated Server
;Set
Target Platform
toLinux
.
Then press build and select a new empty folder named linux_server
as the file destination. Transfer the linux_server
folder to a second empty folder, which will be refered as the [SERVER BUILD]
folder in this document. Add the following Dockerfile
and boot.sh
file to the [SERVER BUILD]
folder:
Dockerfile
FROM ubuntu:bionic
MAINTAINER edgegap
ARG debian_frontend=noninteractive
ARG docker_version=17.06.0-ce
RUN apt-get update && \
apt-get install -y libglu1 xvfb libxcursor1
# Update root CA to ensure outbound HTTPS requests don't fail
RUN apt-get update && \
apt-get install -y ca-certificates && \
update-ca-certificates && \
apt-get clean
EXPOSE 3389/TCP
EXPOSE [GAME PORT]/TCP
EXPOSE [GAME PORT]/UDP
COPY linux_server/ /root/linux_server/
COPY boot.sh /boot.sh
WORKDIR /root/
ENTRYPOINT ["/bin/bash", "/boot.sh"]
Make sure to replace the [GAME PORT]
placeholders with the same port
value as before.
boot.sh
xvfb-run --auto-servernum --server-args='-screen 0 640X480X24:32' /root/build/[YOUR GAME].x86_64 -batchmode -nographics
Make sure to replace the [YOUR GAME]
placeholder with the name of the generated file.
Then, start a command prompt in the [SERVER BUILD]
folder; Run the following Docker commands to create an image of your build and push it to a private registry. See this doc if you want to use Edgegap's private registry.
Using Linux
# build the image
docker build . -t <IMAGE_NAME>:<IMAGE_TAG>
# login, a prompt will ask the password
docker login -u '<REGISTRY_USERNAME>' <REGISTRY_URL>
# add another tag to your image corresponding to the registry
docker image tag <IMAGE_NAME>:<IMAGE_TAG> <REGISTRY_URL>/<PROJECT_NAME>/<IMAGE_NAME>:<IMAGE_TAG>
#push the image
docker push <REGISTRY_URL>/<PROJECT_NAME>/<IMAGE_NAME>:<IMAGE_TAG>
Using cmd
# build the image
docker build . -t <IMAGE_NAME>:<IMAGE_TAG>
# login, a prompt will ask the password
docker login -u <REGISTRY_USERNAME> <REGISTRY_URL>
# add another tag to your image corresponding to the registry
docker image tag <IMAGE_NAME>:<IMAGE_TAG> <REGISTRY_URL>/<PROJECT_NAME>/<IMAGE_NAME>:<IMAGE_TAG>
#push the image
docker push <REGISTRY_URL>/<PROJECT_NAME>/<IMAGE_NAME>:<IMAGE_TAG>
Using Powershell
# build the image
docker build . -t <IMAGE_NAME>:<IMAGE_TAG>
# login, a prompt will ask the password
docker login -u '<REGISTRY_USERNAME>' <REGISTRY_URL>
# add another tag to your image corresponding to the registry
docker image tag <IMAGE_NAME>:<IMAGE_TAG> <REGISTRY_URL>/<PROJECT_NAME>/<IMAGE_NAME>:<IMAGE_TAG>
#push the image
docker push <REGISTRY_URL>/<PROJECT_NAME>/<IMAGE_NAME>:<IMAGE_TAG>
Deploying to Edgegap
Create a new application on the Edgegap dashboard with the following settings:
Application name : Make sure that it's the same name that you used for the
AppName
in your Matchmaker's Director component. Must be in lowercase.Image : Can be any specific picture you want to use to easily recognize your application among others.
Version name : Make sure that it's the same version that you used for the
AppVersion
in your Matchmaker's Director component. Examples may be “demo”, “production”, “v1”, “v2”Container :
Registry : “[URL]”, where [URL] is the value from the credentials you can display on the Container Repository page.
Image repository : “[PROJECT]/[YOUR GAME]”, where [PROJECT] and [YOUR GAME] are the values you used earlier when pushing the docker image.
Tag : “[TAG]”, where [TAG] is the value you used earlier when pushing the docker image.
Tick “Using a private repository”
Private registry username : “[USERNAME]”, where [USERNAME] is the value from your credentials.
Private registry token : “[TOKEN]”, where [TOKEN] is the value from your credentials.
Requirements : Left as is.
Ports :
Click the
+ Add port
link to add a new port, and add the following entries :[GAME PORT]
- TCP/UDP - disable Verifications3389 - TCP - disable Verifications
Testing with client builds
In order to see if the Matchmaker works as intended, you will need the launch the game on 2 separate clients instances and have your Matchmaker release running as well.
Before creating a client build of the game, make sure to uncheck the Auto Start Server Build
option of the NetworkManager
gameObject. Then, depending on what kind of Matchmaker release you are testing, change the MATCHMAKER_URL
to the following:
Local
: change it to "http://localhost:51504
"With Edgegap
: change it to the value shown on the Edgegap dashboard after enabling the release. You do not need to specify the port in this case.
Then, head back to the Build
screen of the Unity Editor, under File -> Build Settings
in the top menus. Select the Windows, Mac, Linux
platform and the correct target platform
for your environment. Then, press build and select a new empty folder named client_build
as the file destination.
Once you launch both clients (either two windows of your client build, or one build window and the Unity editor), have them both select the same game mode
and create their tickets. After a short while, both clients will be connected to a newly deployed server, which you can see the status of on the Edgegap dashboard.
If you are testing with your Matchmaker's online release, there is currently an issue regarding certificates if you enable and disable your Matchmaker release multiple times a week; This will make it so that the requests to your Matchmaker won't work with Unity.
To fix this, simply disable your release, update it with a different name, then re-release it. Make sure to check that the URL in your code is the right one afterwards.
Last updated
Was this helpful?