[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]