Multiplayer Game In Unity with Firebase tutorial! (all platforms w/ Realtime/Auth & Cloud Functions)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys and welcome back to Unicode app today I'm really excited to show you how to create a multiplayer game in unity using Firebase now I already created this video before but it was more of an example demonstration so there were some security issues with this new video everything is going to be sorted and we're gonna look at how two players can actually match together they enter a room and then they can make turn based moves and they can be sent to a back end that can validate them and then put them on Firebase now why am I making a new video with this content well because I actually recently did a talk in Italy in my country about this technology and I actually wanted to bring it to the channel because I thought it would be fun anyway yeah today we are gonna look at the unity project and the code and I also got the presentation right here so you know it's gonna be for once it's gonna be a pretty well structured video or at least I think it's probably going to diverge into something completely different but let's not worry about that anyway Jokes Aside let's get started alright so first of all why would we use Firebase why wouldn't we use any other technology our example in unity there is Photon engine which is really really used a lot in multiplayer Indie Games it's basically the standard so why are we using Firebase instead well there are some reasons okay if you're making a prototype and maybe you just want to test the game or you know it's a game that at the beginning is not going to have many users it might be cheaper to use Firebase because they have a really more lenient plan and you can have a lot of users without paying a cent while with Fulton you on average have to pay that amount for every concurrent user once you go up to 20 concurrent users also if your game you are making is not actually real time but it's more of a turn-based game where you don't worry about timings too much so an example of a game could be like Yu-Gi-Oh Master Duo so that game you have a timer but it's like a 300 seconds timer so you don't care about a one or two seconds lag you could use fire base for that game and also as you can see right here I'm saying 40 cents per million function invocation we're gonna talk about what this functions actually are but basically you can think that a function is actually a move or the the player entering the queue for the game so every time a user does an action so makes a move that's one invocation so you can reach a million invocations pretty fast but it's still pretty cheap anyway the second reason is that it's actually serverless ish so if you use Photon you also need to have maybe a server somewhere or a plugin that can validate the moves with Firebase we're actually not doing that what we're doing is we are invoking Cloud functions which we're gonna see right now and basically these functions are gonna execute some code they're basically some endpoints so Unity calls these endpoints these endpoints will execute some code which will validate the move will put the player in the queue and these functions will actually communicate to the nosql Firebase database and that is actually what is going to be storing the information on the database so the client doesn't communicate with the database directly and This way everything is going to be secure and you don't need a server I mean we could say the the cloud function is actually the back end is the server but it's not self-hosted it's just running on a Google machine that just spawns when you invoke the function so it's really really convenient and cheaper and the third reason is why not maybe you want to learn more about Firebase Technologies this is the best way to test all of the capabilities of Firebase you know and just experiment so maybe you're watching this video just for fun or you know because you want to experiment with Cloud functions real-time database this tutorial is going to have it all and also in the description there is the updated code with all of the stuff so you can already jump into that if you want anyway now that we are done with the boring stuff let's actually test this out because you know before actually explaining how it works let's just see if it works I mean it's gonna work but you know it's just you know test it so we're gonna run the project both on the unity editor which is going to be my first client okay it's going to be my first play here and then we're gonna also have a build right here anyway I'm just gonna show you this actually uses Firebase authentication we are not going to talk about authentication too much but basically I can just uh you know use my account and put the password and I can sign in and I can also create an account so as you can see now I'm signing as my email and this author account is signed in as another email so I can just call this Nico and this is going to be my player one and now this player is looking for an opponent and if you see this is my Firebase database we're gonna talk about this later if you don't know anything about nosql and databases but you know we're gonna explain it later this is just a demonstration as you can see in the branch matchmaking there is this user which corresponds to my email corresponds to my user and we actually put that this user right here is waiting in queue and his name is Nico if we actually go to the authentication we will be able to see all of the users that are signing into our up and as you can see there is me and this uid corresponds to the uid right here all right so there is one player waiting in queue one player is not enough let's spawn another player and we're gonna call it nicochu and now we're gonna make this guy play now there are two players waiting in Q and suffix magic is gonna happen a game ID is gonna spawn and then a new game is gonna be created so basically inside the queue itself a new value got added which is the game ID and now a new game spawned with the same game ID that got generated and inside of the game ID there are a bunch of infos so our example who is playing the game so there are two players the name of the players the user ID of the players uh the game ID and example and also there is a value showing loose turn it is it's a turn based game so we need this value now all of this information got automatically generated by Cloud functions they're actually functions that can run on the Google server and they can be called both via endpoint so just by doing a quest to a certain endpoint or when a branch on the database updates so if there is a new Branch inside the matchmaking I can set it up so a cloud function runs and generates the game data but now if we go back into the client as you can see the client knows that this data was generated and it is also really important once we actually generate the data how does the user know that you know the game is ready well we're gonna see it later but the way it knows is thanks to this game ID so the client is going to be listening for this value and once this value is filled in then they know that there is a game right here and they're gonna go look for it and now we just got a ready up so if we do ready as you can see inside our game a new value is going to appear which is all of the players that are ready and right now this player is ready and now if this player is ready too we're gonna be ready to start the game now the game is just an example so it's just a turned game so I can actually move around this square and I can move one turn at a time so it seems really slow so as you can see the move still hasn't processed but it's actually not slow it can go very fast it's just that Firebase scales on the amount of users you have so right now the last time I tested this application was one month ago so naturally Firebase is like I don't care I don't need to execute requests really fast but as you can see the more moves I will make the faster it will it will work and as you can see it's pretty much I mean it's not real time there is like one second delay but the more users there are the more it's gonna scale so the delay is gonna be uh you know much less but honestly you can expect a one second delay at any time and if you don't care about these delays in your turn based game you can use Firebase anywhere and you can use this setup maybe if the game is something like chess and you're playing hyper bullet you will need a faster delay or people are gonna be mad but as I said if it's a game like I don't know Yu-Gi-Oh or any card based game you don't need to worry about a timer so you can use Firebase just fine you definitely cannot use this for a first person shooter game I actually tried it in the past and no I mean it was fun but yeah definitely don't use that anyway as you can see every time I make a move the database inside the game is gonna add the move to my player info so as you can see these are all of the moves that I make and then the client is going to be listening for the moves and applying them so this is the main gist on how it works and as you can see if I leave the game and the other player leaves it's actually not gonna clean up the the game it's gonna clean up the queue but the game is not gonna clean up so you can set it up so if a player leaves it can automatically remove the game maybe assign a winner but there's a lot of stuff that you can set up on your own once you know you you understand this logic so this is just an example and this is basically what it can do so now we delete all of the data and we can go back in showing how it actually works so right here I actually had a fallback of a demonstration where I just showed a video which is not working but you know we don't need the video because we have the live okay there you go if we have the live feed because luckily nothing goes wrong when I'm recording you know in a room rather than when I'm doing a talk with a bunch of people and you know my computer just shut Downs in the middle of the talk so yeah that was pretty awkward but it worked out in the end anyway let's continue uh no okay no all right so before jumping into the code I want to show you how the actual communication between the client and the database works because it's really important to know the setup so that you know our application can be secure now the client will talk to three different Firebase Services the first service is the authentication Service so the client will just authenticate with email and password in this case using the Firebase authentication Service it will create a user and it will sign in as a user and this will give it a user ID which is a unique identifier for the user and an ID token which is basically a key that tells the Tells Firebase this request is authenticated you are signed in as this user so every other request Quest that we do to the other two services are all going to be authenticated using the authentication Service and we're gonna see how it works later then the other service that we're gonna use is the Firebase database but this is really important we are only reading the database the database is actually locked there is no writing on the database you can only read on the database so if a client tries to do a post request to the real-time database endpoint they're going to get blocked because they're not supposed to write any data they're just supposed to read and listen for updates in the data an example a new move being made or uh you know getting matched with another player in order to actually write stuff on the database you have to go through the cloud function service they are going to do a request to the cloud function which is going to basically spawn a machine executing some JavaScript or typescript code and that code is going to validate the information the client gave it and it's gonna then right to the database and as soon as this Cloud function writes to the data base the client is going to get the response in form of a listener so the client will be listening on some data and it will be sending the request to the cloud function the cloud function will write to the database and now since the client is listening to that database it is going to get the new data so when I said it's serverless issue this is what I meant there is a server side which is the cloud function and which is writing on the database and it is a client side which is the client listening through the database so there is a client and a server communicating but they are communicating through the database now this is really beautiful and all but for which platforms does this run because the Firebase Unity SDK is only supported for Android and iOS but fear not because this system can potentially work on all platforms now it will work without any problems on Android and iOS for Standalone versions so Windows Mac and Linux you can actually still use the Firebase Unity SDK which which will allow you to listen for a value to the database invoke a cloud function and authenticate it but in the documentation it shows that this approach is still in beta so the way you actually are supposed to do this is via the Firebase rest API now the Firebase rest API has all of the functions that the Firebase SDK has so you will be able to do this with apis as well the only thing that is a little bit different on rest apis is listening for a certain data because you would have to use streaming apis but it's doable and you know you can set it up pretty quickly I actually have some videos on how this is done but yeah also if you are doing this just to prototype you can use the Firebase Unity SDK for Standalone as well and you're not going to have any problems and frankly I used the SDK in production as well and didn't have any problems so but you know it's been in beta for at least five years so I have no idea at this point if you are crazy and you actually want to do a webgl application there is also a way to make this work you can still use rest apis like you would do for Standalone you can definitely not use the unity SDK but you can also use the JavaScript SDK so you can inject some JavaScript code which is the JavaScript SDK in your webgl build and I actually have a tutorial on how to do this and a project so you can check that out but again you can still use the rest API endpoints if it's easier alright so next up let's actually go and take a look at how the project is structured now I talked about how I structured my projects in the past in other videos but every time I'm working on a project I try to subdivide the classes in different components that do different things I usually come up with a division of three different types of classes okay there is the apis basically the apis are supposed to be static classes so that there is just one instance for every class so they are static and they are supposed to communicate with services that are outside of uh you know the application so an example they will communicate with the cloud function Firebase API the authentication API the database API or there will just be some utilities that are useful like the serializing and serializing Json so these apis are basically the bare bone of the application and they provide utility and helpful methods that the managers and the antlers can use so an example the out API is going to provide a method which is going to be sign in and you you give it an email and a password and what to do after you actually signed in and that API is going to do that now instead the handlers are on the complete opposite part of the spectrum so instead of being the bare bone so the the lower part of the app they're going to be the higher level of the application they are going to be communicating directly with the UI so an example in the game scene Handler which is the scene where you can actually move the player let me pull it up right here you can see that the UI is pretty simple there is just one button that makes you leave the game well the Handler is going to actually implement this button so in the game scene Handler there is going to be a function that will be invoked when you click the leave button so the address are basically a way to translate the user input into invocations and functions and there should be one Handler for every scene at least and then maybe if you have a specific object like a player that can move you can do a Handler for every instance of that player so in my case in the scene there will be two player handlers underneath every player object and now these handlers are going to communicate with the managers and the managers are what actually contain the logic of the code so the UI does a request to the handlers the handlers communicate to the managers the manager will do all of the logic and will be helped by the apis to do Outsider requests now in this project the way the managers are structured is using a Singleton so the main manager is one instance that is present in the entire application and then from this main manager you can call all of the other managers but you could set this up in a different way that maybe it's more convenient for you you could I don't know do like dependency injections and stuff like that to have multiple managers but you know this is the way I set it up for Simplicity but I think it's really important to know how the project is structured for this case because we are going to see uh for cloud function costs we are going to start at the lower level at the API and we're gonna move on to the manager and then to the Handler so in order to understand how the code is being executed and what it's doing it's really important to have this structure in mind so this is an example of an API it's just the authentication API as you can see you can sign up and sign in we're gonna see it better later an example of an Handler is the game scene Handler so as you can see in this scene there is just the leave button which is gonna just change the scene and then when this scene is actually destroyed it's gonna invoke the game manager so when the scene is changed it's going to invoke the game manager and it's gonna tell the game manager to leave the game so if you see inside the game manager inside the sleeve game we are actually gonna invoke a database API to stop listening for moves because the game is over so as you can see this is how it goes Handler manager API and we will see this later but when we make a move we are actually listening for a move thanks to the database API the database API is going to return a response the manager is going to parse this response and it's going to give this response to the Handler and the player Handler is actually going to modify the UI and execute the move so as you can see it will move the actual player by changing its transform position now naturally the code is a little bit messy but as long as you have this structure in mind you're good all right so now let's start looking at the code now I'm going to look at the code through the slides because I actually simplified a little bit so on the GitHub project it's going to be a little bit different there are going to be some extra lines even there but the main gist of it is the same alright so the first thing we want to look at is how the players actually make a move so the players are already in the game the game has already been created and all we want to do is press a button and send the request to make a move to the cloud function and we want to see how it works so we're gonna actually start at the very end so this is the functions API which makes you call different Cloud functions all right so we're going to use the Firebase SDK to call the cloud function to make a move as you can see we are going to invoke code functions with the name of the function and the data and as you can see the data is just a move object which is just going to be a an array of shoe values the X component and the Y component of the move so this cool function is actually pretty agnostic so it will call any function with any data and it will actually use the functions from Firebase SDK to do an HTTP request and call it and as you can see this function right here is asynchronous so it will wait for the result of the cloud function before for continuing to execute so as you can see inside this code right here there is going to be some code that we run once the request is completed and we're going to check if there were any errors and if there were any errors we are going to send back instead of sending back the data we are going to send back an error so this request response right here that you see is a dynamic object that can either be an error or it can be some data so if there have been any errors we're going to return the errors and also if the data is not in the right format so it's not an array we are going to also return an error but if there haven't been any errors then we're just gonna return the data and now who are we returning this data to we are returning this data to the manager because remember the managers invoke the apis so the game manager communicated with this API to send the move and now this API is going to tell the manager that the request was successful so the game manager code is pretty easy because in this case we actually don't need to manipulate the data we're just sending the move and getting it back so all we need to do is actually tell the Handler everything is fine uh just execute the move so we're not doing any extra code in the manager we are just you know awaiting for this function to be completed and that's it now this game manager function is actually being invoked from the player Handler so as you can see every single tick we are checking if the player has made a move we are first checking if it's their turn and we're checking if the player has made a move so if they are hit any of their keys and if they made a move then we are gonna actually invoke this function but now there is something really important about this we are not gonna invoke it normally but we're gonna invoke it as a cool routine and you may say well Nico why are we doing that well because as you can see this function right here is asynchronous so it's not running on the main thread so we need a way to actually you know divert our code execution to a different thread because we need to wait for this response we need to wait for the function to actually be called and for it to return a response but this update function is running every single tick so if I didn't write asynchronous code right here we would be stopping the main thread which would be really bad in unity C sharp there is a really cool solution to this which is called the core routines coroutines basically allow you to execute code on a different thread but you actually don't need to worry about threads and their lifetime it handles everything automatically for you now I did a video on cool routines in the past so you can go check that out but I'm gonna explain how they work really briefly anyway once the player has actually made a move we are gonna set the move and we're gonna you know just invoke this core routine function right here so now these are how cool routines work basically you can invoke according by doing statical routine hello core routine and inside of it you can actually set some delays so you can await for a certain function to be called just like a sequence functions or you can just wait for five sec seconds and it's going to run this code after five seconds another cool behavior of the core routines is that they are attached to the object that invoke them so this player Handler actually is handling discording so if this player dies meaning I don't know they get killed or we just move out of the scene so this object dies the core routine will actually stop executing and this could be a really cool thing or a really bad thing so if you don't want this to happen you should probably invoke the core routine on an object that you know is not going to despawn so it could be an object with uh do not destroy your load flag or a manager if you want but in my case I don't care about that so I'm just invoking the core routine on the player Handler alright so what does this Co routine do this coating will basically invoke the game manager and as you can see this is the singular tone I was talking about the main manager as a reference to every single manager and from it we're actually getting the getting to the game manager class we are gonna invoke the send move function and then using this syntax right here we are gonna wait until it's done and on this wait for task is actually a helper method I implemented myself uh it's in the main manager but it's basically um you know some sugar syntax for this so you can just do wait until the task value is completed so it will basically wait until this Boolean is through and this Boolean is true only when the request is done because the task is completed so this is a quick way to convert from core routines to asynchronous code so as you can see we're gonna wait for this asynchronous code to be completed and once it's completed we're actually gonna get back it's a response and if you guys remember the response was a request response with some data or an error and as you can see when we go through the manager this request response actually gets implicitly casted to a request State basically the request State only cares about if there has been a mistake or there has been an error it will actually return no data so it will tell you there has been an error this is the error or it's going to be empty so this is the difference between the request response and the request State uh whoops so just to show you this is the request State so it's basically a Boolean telling you if there has been an error or not and if there has been an error it will give you the error while the request response actually inherits from the request State but it also has some generic data that you know you could get all right and now that that is explained as well as you can see we are back here and the ones we actually sent the move if there have been any problems we are gonna you know just print the problems in your case you could like show an error on screen or anything this is just an example so we're just going to print the error and now there is something really important here that is going on under the hoods and I'm gonna show you right now so as you can see as soon as you make a move this flag gets set to false so as soon as you you know do the request you're not going to be able to make another move while the request is processing so you will not be able to spam your left key and make a lot of moves while they are not being processed yet so this flag right here is making sure that you can only do one move at a time so as soon as you do the request it's gonna automatically not be your turn anymore but there is something important if something goes wrong so the request didn't actually go through the the function didn't process it instead of actually soft locking the game we are gonna give you another chance to make another move so it's gonna be your turn again and the user will be able to actually try making another move so maybe if their internet connection dropped out in the middle of the uh you know the game and they are back they do the request and the request fails when they are back online they will actually be able to do the request again thanks to this flag this is something you will see for every request that we are going to look at so it's really important to have this uh safe prevention uh when something goes wrong so to have a fallback uh and you know have a way not to completely soft lock the game and it's also really important to have this flag right here so that the user cannot just Spam a request over and over by clicking a button that is really really important and all right this is how you actually send a move and believe it or not we went through a lot of how the code works because all of the data requests are actually pretty similar now let's do the opposite let's try to listen for a move so we're waiting until another player makes a move and once they actually made that move we need to actually receive that data and update that specific player Handler to make that move now this code looks complicated but it's just a bunch of error handling honestly all you need to care about is that this function right here from the database API will be able to tell you if a certain child was added to a list on the database so as you can see if we go to our database oh it's empty never mind but okay if we write some data like an example games and you know we add our game ID so this is going to be um I don't know an ID of a game and we're gonna have moves at example and inside we're gonna have um a move so this is move one and it's gonna have I don't know it's gonna have two values like this uh this uh code right here will be able to detect if a new Branch was added into this specific node so if I add a new move this code right here will be able to detect it and this on child added callback will run and this on child added is actually a callback that is defined as a function parameter so basically when we are invoking this function we're also telling it what to do when a new child got added we're also storing this listener so that in the future we can actually stop listening for this specific instance instead of stop listening for all of the instances so this is why the code is much more convoluted than it's supposed to but okay let's not worry about that so this game manager will be able to invoke the listen for child and as you can see one of the arguments is going to be the path and the the path that we want to listen to is the current game the player ID and the moves that this player is doing so we're gonna check if that player made a new move and the other argument of the function is going to be the code to execute when the move is gonna be made and now Firebase real-time database is a json-based database so it will return us the response in Json so this manager right here is actually parsing the response and making it pretty so it will turn it into a move and then once the response is actually pretty so it's not some Json but it's a move object it's gonna send it upwards so it's gonna send it onto this callback and this callback is defined as a parameter of the function so the Handler that is invoking this manager is actually going to take care of implementing that code so the API listens for the request the manager processes the request and makes it pretty the Handler will actually move the player and we're gonna see it right now the Handler uh whoops oh yeah we also have some code for listening uh stop listening to moves and this is why we need to have the reference to the database thing to the database listener so right here as you can see this function is actually returning this Abomination right here which is the reference and we're gonna use this reference to actually stop listening for moves at some point but you know it's not important anyway keep in mind this Handler e as one instance for every single player that is in the game so this code is going to be executed as soon as I play your spawns so in our case as soon as the players get to the game scene and you know their instances spawn so as soon as this starts we are gonna invoke the game manager and we're gonna tell them to listen for moves and know when listen for moves for this specific player remember this code is going to be run for every player because there is an instance of this class on every player object and then we're gonna tell it once there is actually a new move invoke this function right here and if there is any mistake just log the mistake right here you could put any other function that handles the mistake maybe showing the error on screen maybe making the player leave I don't know anything this function right here though is what's important this function is going to be invoked when a new move is made and what does this function do well it's really simple it will just change the position of the player so all of this complicated code right here that does all of the stuff all of this game manager that is translating the Json into a move all of this is stacked under the carpet and the actual beautiful code you see is these three lines right here which makes sense listen for a move when you receive the move move the player it's a it's really simple so this is why by structuring the project this way it's really important you know have a mental image of what is going on and you know if you want to change something so maybe when the player makes a move their color changes or I don't know uh they move two squares instead of one square you just need to change this line you just need to look at the Handler you don't need to care about the managers the managers are guaranteed to return you a move or an error so you only need to care about the data that you're getting back you don't need to care about any of the logic beneath it alright and next up I want to talk about user authentication because uh yes we kind of said that we are doing requests to the cloud function and to the database and we are pretending to say okay the database knows that this is me this is my account making the request I cannot make the moves on behalf of another player or on behalf of a player that is not even in my game so how am I actually authenticating my requests well if you use the Firebase SDK it's actually really really simple to authenticate your request because if you use the Firebase authentication Service and you sign in or sign up automatically all of the other services will be authenticated so you don't have to do anything if you use rest apis it's going to be a little bit more difficult but it's possible there are a lot of libraries that do it and I have a tutorial on authentication with rest apis if you guys are interested anyway once the game starts we are going to start on the loading scene and the loading scene is actually going to check if you are authenticated or not and the checking if you're authenticated or not is just a request to the API it's basically checking if you have a token or not and if you are authenticated you are going to go to the menu scene if you're not authenticated you're gonna you're gonna go to the login scene and we're gonna look at the logins in now so this is the Handler for the login scene so as you can see it's gonna have the sign up button and Below there is also the sign in button I probably didn't show it yeah because it's it's literally the same but yeah the sign up button is just gonna do a request to the API AI you can see we are not going to the manager uh right now just because I got lazy but also because the manager is not doing any additional operation so we are communicating uh the Handler is communicating directly to the API if you want to be consistent you could actually put a manager in the middle but you know in this case I didn't so the Handler is directly communicating with the API is signing up using the email and password input fields and it's gonna await for this being done uh always using the core routine and then uh if something went wrong it's gonna I don't know it's gonna show an error on screen and if everything went well we're actually gonna go to the menu scene so it's pretty straightforward and as you can see this is how the application works we have this loading scene we go to the menu scene if we are authenticated if we are not authenticated we log in first and then we go to the menu in the menu you can actually click the sign out button so you can go back to the login and then you can go back to the menu and when you're actually ready you can go into a game so this is basically the setup of how the scene flow Works probably should upload this slide before but you know it's it's whatever alright so how are we actually authenticating the user well with the Firebase SDK is really trivial you just have to do uh you just have to invoke this function give it the email and password and you can await for this being done you can check if you know there have been problems and stuff like that but if there haven't been any problems you actually don't need to return every anything because the Firebase SDK now is authenticated and every other request that you do so like an example let's go this request right here to listen for this child this is automatically authenticated if you signed in and since we signed in at the beginning of the app we don't need to worry about an authentication anymore so it's pretty straightforward all right and this is it the for the client code but there is some part of the code that I didn't show it's the back end it's the JavaScript stuff every time we make a request to a certain endpoint a cloud function we're actually executing some custom code on a Google machine and this code is going to post data on the database it's going to validate data it's going to do some logic and now we're going to be looking at that logic to show you how that works so how do you actually write this JavaScript code and how do you tell Firebase to execute It On Demand well it's actually extremely easy I did a video on this too but basically all you need to do is go into an empty folder do Firebase init in a terminal and you need to install Firebase tools and npm but you know this basically brings you the Firebase command then you can write your code it's going to basically tell you to log in it's gonna tell you the project you want to write your code in and then it's going to return you and index.javascript file uh in the folder you just created and you will be able to modify this file to write some code and once you have written the code then you can just do Firebase deploy and this code is going to be automatically uh you know go on your Firebase project and it's going to be served and invoked when a user requests the function on a certain endpoint or when I don't know a certain node changes on the database we will see this now so after all of this is done you can just profit all right and this is how the back end looks like so these are all of the functions that we are invoking we are invoking one for joining and leaving a queue then that is the really important function that we're gonna see at the end which is the Matchmaker function which is actually the one we are all here about so the the logic that will actually take two players and pair them together which is also a really interesting logic in in it of itself and then we have two other functions which are basically going to be invoked when a player wants to be ready and also when the player makes a move so uh at the beginning of this video when we showed you know uh the functions API in invoking the make move function this code is actually going to be what is invoked and also there is a missing parenthesis here and it's going to be triggering me quite a lot so let's just move to the next slide I'm not going to show you all of the functions because are really really similar um but you know just to show you how the backend works this is the join queue function so other than the data that the user sends Us in this case if you remember the user was sending us their name when they joined the queue but other than sending this data we actually get some context data which is basically what I was talking about before the authentication data we already know if this user is authenticated or not so all we need to do is check if you know they are authenticated so basically this assert out is checking if the old parameter is not normal and then we can get their player ID this way and we know that this ID is them because they are authenticated at that user so we are 100 sure that this is legit so basically we're gonna get their name we're gonna get their ID and we're gonna post some data on the matchmaking uh branch and as you can see we're going to post this data that we saw at the beginning of the video now if everything went well we're going to return a successful request with no data being returned uh if there have been a problem we're gonna return a field request with the error so yeah this is everything this is the code it's pretty neat and now the successful requests and field requests are basically these objects right here they are basically on pair with the request response objects that you know I was talking about before so this one so it's just gonna have is faulted and an error and the same thing is right here all right then we have the same exact code for leaving the queue the only thing that changes is instead of set right here so instead of setting a value to the database on the same node we're gonna remove that key so it's the same exact thing all right and now let's move on to the interesting bit which is the Matchmaker function now this function is a bit long so I subdivided it in a bunch of parts in the different slides so this is not the entire function that is more so the first part of this function is actually taking two players and pairing them up so first of all I'm generating a game ID and this is basically a random set of string and you can also check if the scheme ID is already present before so you can do a request to the database and see if there is a game with the same ID in case you're unlucky but I'm not doing that you you could do that if you want in this implementation also another thing to note is that this function is not being invoked like the other functions so as you can see these functions right here are all being invoked on an endpoint call so on call this function right here though is not invoked when you communicate with an endpoint but it's invoked when a certain reference of the database changes so this oncreate Matchmaker player ID is basically saying as soon as there is a new node inside the matchmaking Branch so a new player uh whoops so a new player got added to the queue as soon as this operation happened then invoke this function alright so thanks to this function we also have the player ID which is basically the newest player that got added to the Q so we already have one pleasure we just need to find the second player so we're gonna go through all of the players in the queue and we are gonna check if we find a valid second player so we're gonna iterate over every single player and we're gonna try to find a player that is not already in a game so if you guys remember when you enter a game your game ID value is Gonna Fill In so we're gonna check for a player that is not already in a game and also a player that is different from the one that you know just got added in right here you could add some more conditions like matchmaking uh I don't know uh points like the player need to have the the same amount of rating uh points in order to be paired and stuff like that but if all of these conditions are met then we're gonna set up a second player and now after this operation if the second player was not found then we just need to wait so we're just gonna return and you know this function is gonna be invoked again once we have uh you know a new player and so the matchmaking is gonna you know uh try to pair two players again so okay now we have the two players that we want to pair now we just need to pair them up right how do we do this now there are two ways to do this that is the dumb way and there is the Smart Way the damn way is just creating the game like without doing any checks the smart way is using transactions now why do we need to use transactions and what are transactions basically if there are a lot of players in our game this function could actually execute multiple times and pair up players that are already have been paid up you might say Nico the first part of this code is actually checking if a player is already paired up so how could this mess up well if this code executes we get the player and then by the time we are posting actually this data this function executes again on a different pair of players we could get inconsistent data so maybe a player could be paired up with two games or a player that is being paired up then left the queue so we could get a lot of different wheel scenarios that are going to be really hard to handle now luckily Firebase makes this easy for us with transaction so when you do a transaction you are basically assuming the data on this branch is not gonna change so when I'm doing this transaction I'm basically saying the matchmaking Branch while I'm doing this operation is gonna stay frozen it's not gonna change so the state of this branch is going to be right here so I will know the state of the branch when I'm doing the transaction and I will be able to write data into it and then post it now when I post the data if the state of matchmaking has changed meaning a new player got added to the queue or that player got paired up with another player then I'm gonna abort this transaction I'm gonna do it again so this code is only going to execute and actually post the data to the database if the state of matchmaking remained the same remain the same as the the one I got right here that's the one we can see right here so this way I can make sure with these checks that the players that I'm getting are actually already and are not in a game and they are still in the queue so this is what I'm checking I'm checking if the player one is in a queue and I'm checking if the player 2 is in the queue and they are not paired up to any games if their seat changed then we should return the matchmaking so we are not gonna do anything right here instead of not doing anything maybe you could call this entire function again so attempt again to pair two different players but in this case I'm not gonna do anything so I'm just gonna stop if instead the data is not corrupted so everything went well I'm gonna actually go into each node so the player nodes and I'm gonna add the game ID key so this is what we saw at the beginning of the the video where as soon as the two players got paired up the game ID got filled in and once the game ID is filled in and this function actually executed we are going to go into this then statement so this then statement the code right here is only going to execute not only if we pair the two players but also if we were able to create a game out of those two players and we are 100 sure there have been no conflicts so once that is done oh this just shows how the transactions work by the way so as you can see this is the code to actually this is from the documentation this is the code to actually start a post on I don't know on Facebook have not Stars their likes uh so yeah stara and put like on a post on the Facebook example so we're gonna do a transaction and we're gonna check if the post exists naturally now if we already have started the post the star count should decrease and we are no longer starving the post else if we haven't started the post or the post doesn't continue any stars we are gonna add our counting this way even if the user spams the star button uh the transactions are gonna execute in order and in the right state so that the light feature will work market and these also a few people put like at the same thing at the same time this is gonna handle that because if anything goes wrong this transaction is gonna fail and this code will be executed again with the new Post State so this is Magic and it works all right as you can see this is what happens when we actually pair the shoe players so the game ID gets added into their matchmaking data and finally the last thing we need to do so the code that we have to execute after all of this is done is actually creating the game and it's just posting a launch a lot of information that we might need about the game like the game ID uh the player info all of that stuff so it's basically this information right here and that we saw before it's just dumping a lot of data on us that the client will probably need so not really much to say about this and finally let's go quickly at the other two functions that we have ready app is pretty self-explanatory basically when the player clicks the radio button the manager the Handler is going to be called we're just going to call the manager the manager calls the API which calls this function right here on the cloud and it's basically always going to the same tracks if the user is authenticated but now he's also going to do a different check it's going to check if the player that is reading Gap is actually already in a game so we are checking if in the matchmaking field this player has a game ID set and if this game ID is you know an actual game and we are gonna return this game ID and we are going to use it to actually ready up in this in this game so we're gonna get the game ID from the queue and then we are gonna make the player ready up in this game so as you can see in the data the input of this function we actually don't specify the game ID I'm just telling the cloud function to ready me up but the cloud function actually doesn't receive the game and this way it's 100 secure like the player cannot ready up in someone else's game or in a made-up game it's actually gonna check the player game first if it exists and then it's gonna do the ready up and as you can see this is exactly what happens and finally this is the code for making a move is the exact same thing that's really up except that now as an input we get the vector of the moves and we also have this helpful method which is going to assert that the move is valid and you know you're not moving two squares or you're moving I don't know diagonally or something so uh this is just a code to check if the move is valid but this code could be anything in your uh you know in your application so you could change this to anything and you would just have the same system that works but with a different move you could change this to the rules of the game of chess for example and it will be the exact same thing system because we are always posting some Json data some text uh in the end so you could just make the move object something different change this function and everything else will work the same and finally once the checks are done we're gonna again check for the game ID and we're gonna post a move on that game ID another important thing is that when we're posting the move we actually don't want to um you know we don't want to set it underneath player moves we actually want to set it underneath player moves then generate a random key a unique key and then add that move so that it gets added in as a child so as you can see when we create a new move there is this new key that gets randomly generated well probably not randomly generated but it's a unique ID of this Branch so we want to generate the string of characters where we can place our move inside so that you know our new children gets added to the moves object rather than replacing the entire moves object so in order to do this we use the push function if you do this push function right here it basically means instead of setting the data in the moves Branch so right here set the data in the moves branch on a unique key underneath the moves Branch so we go to the moves reference and we do push to generate this key and then we're going to set the data under that key and the data is the move that we're making and after everything is done we are just gonna return a successful request alright and this is is it I showed the back end I showed the client side but now there is one more thing to show and there is Firebase database rules because we have been talking about security a lot and if we go to actual Firebase as you can see I can read all of the data because I'm on the console so I'm actually an admin I can read all of the data of the database and I can write to it but as I said before the users are not should not be able to write any data but also they should be limited in the data they can read they should only be able to read the data related to their own queue and also related to their own games they shouldn't be able to read the state of another player's game if they are not in it so thanks to Firebase and I made a more you know a bigger more browse tutorial just on Firebase database rules if you want to check that out but just thanks to Firebase we can create some rules in a JavaScript format to show a Firebase which requests we should negate and which regressed we should approve now the first thing to note is that there are no right rules so there is no way that a user can write on the entire database and then in the database we have two main nodes we have the matchmaking which is the queue and we have the games node so we're writing a read rule uh for both of these nodes so the read is only going to be allowed under certain conditions if you are in the matchmaking node then you need to be authenticated as that user ID if you want to read the data underneath that user ID what does this mean I can show you right now so if I create a new node which is matchmaking at example and then we create user ID one our example this is going to be the random not random but we're gonna it's gonna be the jumble of characters that makes the user ID so instead of here there is some data okay which is going to be basically the game ID so as you can see this data right here I can only access it if I'm signed in as a user id1 and we can actually test this so as you can see the rules are right here we can actually use the rules playground to test this so I'm going to try to do a get request to matchmaking and then user id1 and I'm going to run the request and as you can see it's going to be denied if I try to authenticate and I run it it's going to still be denied because I'm authenticated but not as a certain user ID if I change the user ID to exactly what I'm trying to get so user ID one only then I will be able to be allowed so this is how the matchmaking rules work and then we have a very a much more strict rule for the games because if you want to read the information about a game we need to make sure that you are actually in that game so what this code is doing is basically getting the entire matchmaking Branch so it's getting the entire database but I mean it's not actually getting the database because this code is executed on the on Firebase itself so it already has all of this information like it's not expensive to do the to do this course and I read also on the documentation these are free so you can go to the root of the database okay go to the matchmaking node and then we have attacking if inside the user ID that you know the user is authenticated with if the game ID value is equal to game only if that value is equal to the name of the thing we're trying to read only then you should be able to read it and we can test it right now so we can try reading games game one another example and as you can see it's not gonna work I mean okay sorry there you go it's not gonna work because this fails but if we actually go into data and inside the user id1 we add a game ID field with the game one in it if now we go to the rules and we do the exact same requests so we need to be authenticated with the user ID user id1 and the location needs to be games game one then we will be able to read correctly only because the data was there so this way only requests that are legit can be done to the database nobody can steal our information and most of all nobody can write anywhere that's something you can never do because the cloud function takes care of writing not the client and the cloud function needs to take care of writing because this way they can validate the data alright so this is it this is the video we've seen how to create you know some good project structure or at least the structure that I use for my projects to keep everything organized we've seen how to create asynchronous code in unity or how to go from synchronous main threaded code to asynchronous code in unity using Chrome routines we also looked at how to listen for certain events in the database using the data base API and the Firebase SDK and then we've also seen how to actually write some JavaScript or typescript code to actually execute some functions in the cloud without a server so that's pretty fun and then finally we learned how to use transactions on that backend Hood to actually assure that the data is you know not corrupt or that we don't corrupt the data we don't get race conditions when we modify it and then finally finally we know now how to secure our database with cool and secure Firebase rules and I know this video is a little bit all over the place like it's not like a regular tutorial where we only talk about one thing it's more showing you what you can do with Firebase and the main you know caveats and the interesting things about creating a multiplayer game the main things you have to worry about and you know the the main tricks that you can use in order to actually accomplish this but you know this is what I wanted to show you and I hope you guys really enjoyed this video and I think this is it yeah I mean if you have any questions you can write them in the comments this is just from from the talk so it wasn't supposed to be here but yeah thank you ever so much for watching the slides are going to be in the description if you need them for whatever reason and but also the code the full code is going to be on my GitHub it's all going to be in the description so you can go check it out let me know if you enjoy this style of content I'm gonna try to create more and this was really amazing to make thank you ever so much for watching this video till the very end you are the best leave a like if you enjoyed and I'm gonna see you guys in the next video see ya
Info
Channel: uNicoDev
Views: 9,885
Rating: undefined out of 5
Keywords: unity, game, gamemaking, tutorial, how to, multiplayer, firebase, cloud functions, realtime database, authentication, backend, javascript, typescript, nodejs, firebase sdk, unity3d
Id: pjOlGwxYNXs
Channel Id: undefined
Length: 54min 36sec (3276 seconds)
Published: Sat Jan 14 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.