Getting started with Realtime Database on Unity - Firecasts

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[MUSIC PLAYING] PATRICK MARTIN: Hi, everybody, and welcome to another Firecast. Today, I want to talk to you about how to integrate Realtime Database with your Unity game. If you're looking to sync data in near real-time with a whole ton of users, you've come to the right spot. When I say near real time, this is a subtlety that only really matters to game developers. Since Firebase is first and foremost a database, it favors sequential correctness and data integrity over getting bits from one phone to another as fast as possible. This means that if you're trying to make your next mobile MOBA as low latency as possible, you probably want to look elsewhere. But for most games, you'd want to ship on phones, say, auto battlers, asynchronous multi-player games, or turn-based board games, Realtime Database is plenty fast to suit your needs. Let me show you what I want to do today. I have this game called "Farmers of Landshallow." I intend to eventually make this into a multi-player worker placement game. But up until now, I've just been tooling around with some local behaviors. I can click Start Game. It'll ask me for my name. Then I can choose a color just by moving this slider corresponding to the hue and hue saturation value color space. Then I click Register, and I'm here in the game. I can click on either corn, wheat, or carrots to start farming. My little farmer maple walks over and idles for a bit to show this off. Let's look under the hood to see what's happening. The core of my game is two classes. One called Player Data just holds the base data for a player in this game. This will grow in complexity as I track more stats related to a player. The other, called Player Behavior, acts as the glue between the raw data and unity. Other mono behavior scripts listen to its events and configure themselves based on its state. It has a player data that represents its current state and an update player function so I can easily update that state. I also provide convenience functions to pull out the relevant bits of information from the backing player data. As a player interacts with the game, the resource they are farming changes fairly often. Therefore, I also provide a convenience method that changes the resource field. The main reason why I broke up the player into two different classes is so I can write out data with PlayerSaveManager. It only interacts with player data converting it to and from JSON using PlayerPrefs as its backing [? store. ?] Throughout the game, as I choose what I want to farm, I have a small script that saves that change. Unfortunately, I can't say what exactly each of you are saving to disk at home. The good news is that if you use Unity's PlayerPrefs at all to save user data, you've already done most of the legwork to start moving stuff into the cloud and eventually making your game multi-player. Just know that PlayerSaveManager will be replaced by whatever you use to control your interface with PlayerPrefs. And some of you may prefer to have that logic in player behavior. Now, it's time to make my game multi-player. And my first step in doing that is to move my player data into the cloud. I already have a Firebase Project set up and hooked up to my Unity Project. If you haven't done this yet, go watch this video on how to create and set up a project and then come back. Next, I need to enable the Realtime Database. So I'm going to open up the Firebase console in a browser and click on Database on the sidebar. Scroll down a bit and choose Realtime Database. At the time of this recording, Cloud Firestore isn't available for game developers. But even once it is available, Realtime Database will likely be very well positioned for many of the games space use cases. You can find a blog post comparing the two here for more information. I'll start it in test mode for now to get things going. An important note-- this makes your database open to the entire world. Eventually, via security rules, you can add sophisticated logic around reads and writes to your database. This means that the Realtime Database never needs to be hidden behind customs and points for security purposes or even to validate more complex gameplay logic. Firebase will keep reminding you of how dangerous public rules are, so pay attention to it. Now, it's time to plan the Realtime Database plug-in. Recent versions of Unity only support .Net4. So I'll pull the plug-in from that directory. If any of this sounds unfamiliar, refer to the Getting Started Video. We're ready to start persisting data into the cloud. First, I need to initialize Firebase. I create a little script to do that real fast. If you've ever seen any of my other videos, this won't be very new. I just start an asynchronous task to initialize Firebase and wait for its success on the main thread. Continue with on main thread is a Firebase extension method designed to help you mix tasks with Unity game logic more easily. Right now, I don't care about the error in a failure, so I just log it. Once Firebase is initialized, I just trigger an event. I'll add the script to my Unity Editor, make my Start button not interactable, and hook up a Unity event to re-enable it in the editor. I'll test this out real fast, but blink, and you miss it. Now, I went to actually save data into Realtime Database. But to do so, I'll take a brief aside and explain how Realtime Database structures its data. You can envision your entire database as a large JSON object. Every node may have a value, say, and int or a string, an array, or another complex object in it. You can write the address of any node in this tree, as you would write a location of a file or folder on your file system. Every time you write a slash, you go to that node's child. You can then hold onto a reference to any node in the database and read and write its value, either by placing basic types, like strings or ints in it, adding arrays, dictionaries, or by serializing JSON in and out of it directly. To see how this helps us migrate our data out of PlayerPrefs, I'll update PlayerSaveManager. First, I retrieve a database. Note that I can only call a default instance now because I called check and fix dependencies async in my Firebase init script. Failure to do this may result in an exception here on some Android devices. Then I change SavePlayer to write into it. I'll use my player key variable as the path under which I'll store my player data and fill that with JSON object data of my player. I should note that I'm choosing to submit JSON directly in the interest of simplicity. Ideally, I'd call set value async with an i dictionary or submit individual values for each field in the player. Next, I need to see if a save exists. Now, I've run into a bit of a conundrum. Most of the database calls from here on out return tasks. I can't just rely on a bool coming out synchronously. I'm going to use async/await to handle these calls. But know that if you still use the .Net 3.5 run time and an older version of Unity, this won't be available to you. So now I get a reference just as I did above but await the result. Then I check .Exist which will return sometime in the future. For load player, if I use the existing logic, I'll basically be calling GetValueAsync twice in a row. This isn't very useful. So I'll break the rules of [? dry ?] and repeat myself. Finally, erasing is really easy. Before I can run, I have some errors to fix. First, I'll jump into this exception and load scene for player save state. Save exists became an asynchronous call. Rather than yielding as I do above, I'll actually wait on this in a Coroutine. First, I'll add a Coroutine field. I'm going to use this to prevent Trigger from being called multiple times in a row if we're in the process of executing logic. So in Trigger, I'll check if Coroutine is null. And if it is, I'll make Coroutine equal to start Coroutine. I'll also generate LoadScene Coroutine here. And remember to set Coroutine to null when it completes. So here, I'll get the task returned from saveExist and query it in a call to wait until. Finally, I choose which scene to load based on the result of saveExist tasks. If you're wondering on when to use async/await or Coroutine, my rule of thumb is that I use yield when I don't think my logic is going to depend on anything on my Unity Game logic. As soon as model behaviors come into play, I switch over to Coroutines. I cover this and more on my blog post on how they handle asynchronous logic in Unity. You can find it in the description below. I also need to fix sync player to save. I'm going to use a Coroutine just like I did in LoadScene for playerSaveState. But this time, I'm going to turn the entire start function into a Coroutine. You can only do this with some Unity callbacks, start being one of them. Then I renamed playerData to playerDataTask, wait on that, and pull out var playerData to continue as I did before. Now, I register Super Bob, the mighty farmer. I have an affinity for the color magenta. When I first started game development, this was the color commonly used to indicate a transparent pixel. So I never got to use it. I'll click Register, then jump over to my database. My data is already in there. Let's see how fast this is. I'll put my player window and database window next to each other and start selecting Resources. And it's instantly synced to my server. And look, I can even stop playing and start again to get right back to where I was. There's one more thing to cover, though. It's awesome to see the syncing with my console open. Well, it'd be really cool to get data back into the game in real-time. So I'll make a few small changes. First, over in PlayerSaveManager, add a field for the last player data I received. When I go to save data, I make sure not to do any redundant rights. Because of how I wrote PlayerSaveManager, this is necessary to prevent an infinite loop when syncing with the server. Now, I'll create a Unity event that holds player data and use that to notify Unity-based listeners that's something changed in player. If you look at a database reference, there are a number of events that it might raise. Most of these deal with child nodes, such as child added and child changed. These are really helpful if you're listening for more structural information, such as a new player joining a game. I will choose to listen to the value changed event. This listens for a change to its node and any of its children. If you recall from before, my player data contains three nodes-- color, name, and resource. Color itself contains four nodes for the red, green, blue, and alpha channels. By listening to value change, I get a notification if any one of these fields changes at all which is what I want. Value change has another nice feature. It has always called when you register our listener on it. So I never have to make an initial call to get value async if I use this event. Now, in start, I'm going to cache the database reference. Then I'll immediately register for events. It's super important that I unregister in the onDestroy, because of the difference between how C# and Unity handle memory. Not doing this almost guarantees and null reference exception. Finally, I implement handle value changed. I'm going to get a callback whenever value of this Realtime Database node updates. When I get that callback, I'll retrieve the JSON value. Then I'll check for this to be null. Even if you have data online, you have to do this check on the off chance that you receive a null value from your local cache first. Finally, I deserialized the data I received. If you recall that last player data field, this is where I set it. Then I notify listeners that there's new data to be had. Now, I'll update sync-player to save. My changes are relatively minor. Rather than just retrieving the data in the beginning, I add a listener for when the saved data is updated. Then I implement this Handler. All I do is pass the data into player behavior. Finally, I set my player data to the cache data. This is just a stopgap in case I somehow missed this event. Let's give this a try. I can open the console and my game side by side. Then now only see that as I click the Resource field updates, but I can rename my farmer and even change their color. And that's it. We've gone from a lonely single player farming game to the beginnings of a massively multi-player online farming empire. What should you start to look into now? Don't forget about those rules I told you about way back in the beginning. I'd recommend locking down your database as soon as you can. In fact, I better do this before I hit Publish on this video. I don't want one of you to set my farmer's alpha to zero. Also, now that you persist one player, it's easy to replace that player key variable with a unique user ID and get a bunch of people farming at the same time. You can also create in-game chats or lobbies for multi-player games. Maybe hooking on Firebase Dynamic links to create personalized secret invite links to ensure a game has always played amongst friends. You can also use Hooks for Firebase Cloud Functions to trigger logic after a Realtime Database write. Perhaps in a game of rock, paper, scissors, each player records a move. Then our server function automatically updates the score and resets the database for the next game. Why don't you tell me what you plan to make with the Realtime Database, or what you want to learn about next either in the comments below or @pux0r on the Twitter. See you next time. [MUSIC PLAYING]
Info
Channel: Firebase
Views: 55,284
Rating: undefined out of 5
Keywords: Getting started with realtime database, realtime database, firecast, firecasts, firecasts for firebase, firecasts for firebase developers, firebase, firebase developers, google developers, Patrick Martin, get started with firebase authentication in unity, firebase and tasks, GDS: Yes;
Id: MbIH4QT3xF8
Channel Id: undefined
Length: 14min 38sec (878 seconds)
Published: Thu Dec 19 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.