Firebase Multiplayer Game & Matchmaking in Unity!
What I will show you today…
I have put together a simple example of a turn-based multiplayer game that uses a custom matchmaking system using Firebase Realtime Database with Unity!
Today we’ll completely crack it open and you’ll read about how the project and the database are structured and how the entire system works so you can easily integrate it and expand it for your own Apps!
Here’s a glimpse of the final result:
Before we begin…
- Do you find learning easier if it comes from a video? If so, you can find the video lesson of this article right here! Sit back, grab your popcorns, and start watching!
- Alternatively, if you have just clicked on this article for the code, here it is! Enjoy!
What we’ll be using…
Here’s a recap of the tools we’ll be using in order to make this project:
Let’s begin!
Before diving deep into the code and the different functions, let’s take a moment to grasp what exactly we are trying to accomplish:
- Players should be able to join and leave a matchmaking queue
- Players in the queue should be paired up by the system into a game
- Once they are paired, they should be able to ready-up
- Finally, when inside the game, they should be able to share information and moves with each other and exchange turns
Let’s talk about the project structure!
The easiest way to read someone else's work is to have an understanding of how things are structured and why they have made a choice to structure things in that particular way.
Scenes
The Unity project is composed of 4 different scenes.
- LoadingScene
Used mainly for the initialization of the Firebase SDK and managers. It will redirect the player to theMenuScene
afterward. - MenuScene
It prompts the user to enter a playerId and to join the queue. Once they do so, they will be redirected to the MatchmakingScene. - MatchmakingScene
In this scene, the client waits for the server to get paired with an opponent. Once that is completed the user will be prompted to ready-up. Finally, when all players in the game are ready, it will redirect the player to theGameScene
. - GameScene
The player is able to use their arrow keys to move their cute little red square character whenever it’s their turn. Whenever they make a move they will send it to Firebase so the other player can retrieve it on their end.
Scripts
The scripts are organized in 4 different folders. Now, of course, you don’t need to remember all of them, but if throughout the read you need a refresher on what each component does, you can come back here!
- APIs
The lowest level scripts take care of basic utilities.
-DatabaseAPI
takes care of posting, pushing, retrieving, and listening for any data on the Firebase database on any path.
-StringSerializationAPI
takes care of converting JSON into objects and vice-versa, using theFullSerializer
library I linked above. - Managers
They rely on the APIs to take care of higher-level tasks such as joining a queue or submitting a move.
-MainManager
takes care of initializing all other managers. It also has a static reference to itself (singleton) making sure that all managers are always accessible from anywhere.
-MatchmakingManager
takes care of joining and leaving the matchmaking queue and listening for when a game is found.
-GameManager
takes care of the ready-up and of submitting and retrieving moves. - Handlers
They rely on the Managers and take care of all of the UI elements in a specific scene or of a specificGameObject
. They are 5, one for every Unity scene + the PlayerHandler which takes care of each cute little square (player) instance! - Serializables
They are just data objects used for storing and sending data easily to the database.
-Move
stores a single move the player executed
-Gameinfo
stores the info about the current game
In this tutorial, we’ll take APIs for granted, as they simply interact with the Firebase Realtime Database SDK to make simple database operations. If you are new to using Firebase and want to learn more about the basics of the SDK, I strongly suggest you watch my small tutorial video series on how to make a Chat messaging system in Unity using Firebase Realtime Database.
The Loading Scene
We spoiled already pretty much everything there was to say about this scene.
- It initializes all of the managers:
- It initializes the Firebase Realtime Database SDK and moves the user to the
MenuScene
:
The Menu Scene
Will redirect the user to the matchmaking scene once they are ready to join the queue.
One thing to note is that I am storing the currentLocalPlayerId in the MainManager
so it can be accessed from other classes too!
Moreover, for the simplicity of this example, we are making the user choose their own ID. This means that, if two players have the same name, the database will think they are the same person!
To solve this problem we’d have to implement some sort of Firebase Authentication system into our app so that users can be uniquely identified via the Firebase userId
.
Again, I talk more in-depth about Firebase Authentication in my Firebase Chat tutorial series, more specifically, in the second episode so you can go there and have a look if you want to expand this project!
The Matchmaking Scene
As soon as we enter this scene, the player will add themselves to the queue with the playerId they chose. They do so by posting a value of “placeholder” in matchmaking/{playerId}
(This is done by calling the SetValueAsync()
function on the database reference path).
Using the DatabaseAPI
, we will also add a listener to that same path on the database (matchmaking/{playerId}). This means that a certain function will be called on the client side whenever that “placeholder” value is updated with something else. (This is done by adding a ChildAdded event observer to the database path reference).
When the server wants to match us up with another player, all they have to do is replace that “placeholder” value with the id of the game we are going to be joining and our client will be instantly notified.
The Matchmaker Backend
It’s finally time to address the elephant in the room. How are we actually pairing two players together and starting a game? The code that does this can’t definitely be executed on the client-side; instead, we need a server that has the authority, what I call… the Matchmaker!
Luckily for us, Firebase is on our same page! We can easily accomplish exactly what we want using the Cloud Functions Firebase service. They are basically Javascript functions that get executed server-side when a certain event occurs.
If you want to learn more about how to set up Cloud Functions and the different types of functions out there, you can check out my video tutorial on them!
Here’s the Cloud Function that gets executed in the backend:
Let’s analyze this step by step:
- We initialize the function on line 7, telling it to run when new data is created on the path
matchmaking/{playerId}
a.k.a. a new player joins the queue. - On line 10 we generate a random string which will be the
gameId
from now on. - On line 12 we retrieve the entire queue from the database using the Firebase Admin SDK, then on line 14 we iterate over every player and look for one that still hasn’t found a game. If such player exists we store it in
secondPlayer
and we generate a game on line 22. - We now have to communicate with both the player that we found in the queue and the player that just joined the queue (which triggered the function to run), that their game has been found. This is done on line 22 using a transaction; this way, if any of the players leave the queue or get into another game while the matchmaker is working, the call will be aborted (line 25).
- Lastly, if the transaction succeeded we create a new variable with some simple information about the game, such as its id and the number of players on it (line 35). Finally, we just post this information in the
games/{gameId}/gameInfo
path on the database, on line 43.
And that’s it! Now the two players are paired together and they can also retrieve all the information they need about the game! The server did everything it needed to do, let’s head back to the front end!
The Ready-up System
When the client-side detects that a game is found, it will run the GameFound()
callback and also store the gameId
in the MatchmakingSceneHandler
class:
The code here seems pretty self-explanatory:
- We use the GameManager
GetCurrentGameInfo()
method to retrieve the information the Cloud Function has pushed on the pathgames/{gameId}/gameInfo
earlier (line 7). This is done using a Firebase ValueChanged listener on the location so that even if the matchmaker takes its sweet time pushing the game info, the client will wait for it. - We start listening for all of the players being ready (line 12) and when that occurs, we redirect the player onto the
GameScene
(line 16).
As of right now, the only question you might be asking yourself from this snippet of code is “how do we know when all players are ready?”. Let’s go inside the GameManager
and find out:
The function SetLocalPlayerReady()
gets called whenever the player clicks the Ready button in the Matchmaking Scene.
It will basically post a value of true
on the path games/{gameId}/ready/{playerId}
. Pretty simple!
The ListenForAllPlayersReady()
will trigger a function every time a new player posts on that same path. Using the readyPlayers
dictionary, it will keep track of how many players are ready (line 19). If all players are ready (line 21), then the onAllPlayersReady()
callback will be called (line 23).
And this is basically it for the Matchmaking Scene! We now know both of the players are in the same game and they are also responsive, so we can start making them play… in the Game Scene!
The Game Scene
In this example, we will always have 2 players: the local one and the remote one. In your implementation of this example though, you might want to create a game with more people, so I tried to keep the system agnostic to the number of players.
That’s why, in the GameSceneHandler class, we are going to iterate over the number of players (we get this from the gameInfo
) and we’ll spawn a new cute little square for each one of them.
As you might have noticed, the playerPrefab
contains the PlayerHandler
component. This class will be in charge of sending or listening for new moves for that specific player that got instantiated.
One thing to note is that on lines 16–17 we pass the PlayerHandler
some basic information about the player they are… well… handling! This information is the playerId
and also whether the player is the local player or not. (This data will come useful in just a sec).
Now that we have spawned our players, let’s look at their logic, by peeking into the PlayerHandler
class:
If the player is local (meaning it’s the one we control with our arrow keys), then we color them red (line 16) and also we listen for when it’s our turn in the game; when that’s the case, we update the flag isLocalPlayerTurn
to true (line 20).
The way we check if it’s the player’s turn or not is by simply adding another Firebase listener this time to the path games/{gameId}/turn
. If it helps you, scroll up to the game database structure image and take a look at it again.
If it’s the player’s turn, then we check for their arrow input and, if there is any, we execute their move locally by running the ExecuteMove()
function and then we send the move to the database:
When we send the move, we are actually just pushing the Move serializable object on the database at the path games/{gameId}/{playerId}/moves/{moveId}
. The moveId
is a random uniquely generated ID by Firebase (generated using the .push()
function from the Firebase SDK). You could have your own IDs to differentiate the moves or they could just be numerically ordered.
Finally, on line 22, we will give the turn to the other player, by changing the value at the path games/{gameId}/turn
to the other player’s ID.
If the player is not local, instead, we start listening for their moves by setting up a new child listener on the path games/{gameId}/{playerId}/moves/{moveId}
.
Whenever we get a new move, we will run the ExecuteMove()
function to sync the remote player’s position locally.
This way, the two players will alternate sending and retrieving moves. We are done!
Note that the fact that this is a turn-based game only made our codebase slightly more difficult. If you just want the players to submit moves whenever they please, it’s just as simple as removing the if statement on line 13 and maybe adding some sort of delay in between moves, so that you can’t send 60 moves per second!
Expanding this example…
Authentication!
As of right now, players are able to choose their own playerId by typing it into the input field. This is not a good idea! If two players end up choosing the same name, the system won’t be able to distinguish them in the queue or in the game itself. Consider using Firebase Authentication to keep track and uniquely identify your users.
Cleaning up the Database!
As of right now, whenever the players decide to leave a game or just turn off their device, the game instance will remain there, rotting on the database! Consider adding a scheduled function that checks periodically for games that have been idle for some time and just deletes them.
A note on security!
This article provided a simple multiplayer example using Firebase and Unity. If you are looking to implement this into your own project though, you have to care about security.
As a first step, you can learn more about how to implement solid security rules for your Firebase Realtime Database either from the docs or from a mini-tutorial series I did on my channel a bit ago.
More importantly, you can expand this example by adding some sort of server-side validation with Cloud Functions to the moves each player sends the server, just in case they find a way to create a modified version of your game and start sending the server illegal moves or start forcing the turn to always be theirs. But this is beyond the scope of this tutorial!
In conclusion…
Firebase Realtime Database can be a viable solution for simple Matchmaking and Multiplayer systems, especially in slow-paced games. It can also be easily integrated with other Firebase services such as Authentication.
It was even used in the Google doodle Loteria! back in December 2019!
Whether you are reading this just casually or you are actually working on a Multiplayer game, I hope this article has helped you get a better understanding of what is possible to do with the Firebase services or at least pointed you in the right direction for your upcoming projects!