PATRICK MARTIN: Hi, everybody, and welcome
to another Firecast.
User-generated content in games is all the rage
with the kids these days, so much so
that making it easy for your players to create and share
content in your game might as well be table stakes.
I'm going to show you how easy it
is to host whatever content your users create in the cloud using
Cloud Storage for Firebase, our powerful service
for storing large binary objects,
like videos and screenshots.
But first, I must create the greatest game
the world has ever seen.
I think the game's going to be about capturing a photo
and sharing it.
[MUSIC PLAYING]
So let's see.
I'll create a scene.
Maybe add a script to rotate a note over time
and make this rotate a camera.
I'll also throw some cool cubes and boxes here.
In the industry, we call this a gray box prototype.
Let me just make sure this all works.
I'll also throw in a button that says Capture.
Here we'll capture a screenshot [INAUDIBLE]..
You may be wondering, hey, Patrick, how do I
capture a screenshot in Unity?
This sounds like the greatest viral marketing
scheme I've ever heard of.
Well, that's an easy one.
I'll create a new CaptureScreenshot script.
I'll make this an IPointerClickHandler
and make OnPointerClick start a coroutine.
Then wait until the end of the frame just to make sure
everything has finished rendering.
Then I just capture a screenshot with ScreenshotCaptur
e.CaptureScreenshotAsTexture.
I want to raise a Unity event with this Texture2D,
so I'll define the event class at the bottom.
Add the OnScreenCaptured event at the top of my file,
and invoke it.
I can easily show this in-game using a raw texture component.
But I promised you that I could share this into the cloud
somehow.
Firebase just happens to have a product called
Cloud Storage for Firebase.
This lets you host any kind of binary content you want.
Firebase's own "MechaHamster" sample actually
uploads replay data here.
This versatility is great for screenshots
as well, or even animated GIFs.
So first, make sure you've created a Firebase project
and have properly initialized it.
See this video link in the description
below if you haven't done this yet.
Then I'll go into my project and enable cloud storage.
I'm going to make my storage bucket totally
open by modifying the rules a little bit.
So I'll click on rules and change allow read,
write to if true.
I'll comment out the rest for now.
So hold up.
It's time for a quick security timeout.
There's no secret code in your application
that restricts access to your Firebase backend.
The data in google-services.json or GoogleService-Info.plist is
trivially retrievable by anyone inspecting your shipped game.
This means that if you leave rules open like this,
literally anyone can store, retrieve
anything in your cloud storage instance at your expense.
That means that if you don't want hackers turning your cloud
storage project to the latest warez repository,
you're going to need to change these rules to literally
anything other than what I've written here.
Rules themselves are a complicated topic and way
beyond the scope of this video.
But your two best strategies will be to generally set
allow write to false to turn most of your storage bucket
into read-only, then to integrate Firebase
authentication to only allow users to write to user
specific paths in your storage bucket.
You can also put rules around file size, type,
and basically be as specific as you possibly can.
For further information, check out this video on rules.
Link in the description below.
Back onto integration.
I'll put in the Firebase Cloud Storage SDK.
Since I'm using Unity 2019, I'll use the plugin
in the dotnet4 folder.
As with any Firebase project, I need
to decide how I'm going to initialize Firebase.
Today I'll just create a typical Firebase init script here.
Go and make the async call and raise an event
when it succeeds.
Just drop this into the scene.
And the only thing that's going to use Firebase
is this Capture Screenshot button,
so make it not interactable on load.
Then when the Firebase init script
raises the uninitialized event, I'll make it interactable.
I'll test it real fast in the editor,
but this should finish immediately in the editor.
Now that I've laid the groundwork,
I'll create an UploadScreenshot script.
I'll create a public function called UploadScreenshot
that takes the Texture2D from my CaptureScreenshot script.
I'll have this immediately kick off an UploadScreenshot
coroutine just so I can perform asynchronous actions
synchronously.
Uploading the screenshot will be a three-step process.
First, I need to create a storage reference.
Second, I need to convert my screenshot's Texture2D
into bytes to upload to the internet.
And third, I need to add any metadata I think is relevant.
So to start, what is a storage reference?
Think of a reference as a sort of super path.
Getting a reference does not actually do anything
to your cloud storage instance.
It's just a tool with which you can
express your intent in a way that Cloud Storage understands.
Some of the basic actions that you
can perform with a reference are uploading data,
downloading data, getting or setting
metadata, and deleting data.
And in case it isn't clear, since it can upload data,
the path in your database represented by the reference
doesn't have to exist when you create it.
So to create a reference for my screenshot,
I'll call storage.GetReference.
For now, I'm just going to want a random path to store it.
So I'll use Guid.NewGuid.
Now, I need some actual bytes to upload, which
I can get with EncodeToPNG.
And finally, I create an uploadTask with PutBytesAsync.
Since I have a task, I can use WaitUntil
to wait for it to complete.
If I want to handle any errors, I can check for Task.Exception
to be non-null.
I probably want to yield break here.
At this point, I know that I've uploaded the data,
and I want to check that it worked.
I can use GetDownloadURLAsync to get
a URL, from which I can download the data I've uploaded.
I can just log this out.
Let me hook up everything.
Add my script to the scene.
Hook it to the CaptureScreenshot event.
Then run this real fast.
Take a screenshot.
Wait for it to upload.
And here I have this URL to check.
If I open this in a web browser, I get the screenshot.
Now, a note about this URL.
Firebase is a very web-friendly SDK.
And this URL exists primarily so that web developers
can add an image to an IMG tag in a website.
Instead, you should use GetBytesAsync
to download a Cloud Storage file into memory like this.
I'm going to use long.MaxValue for my maximum download
size, which is basically a fancy way to say,
I don't really care about the maximum size of this download
right now.
GetBytesAsync downloads a file into memory,
which means that your game will crash
if the file is larger than the amount of free memory you have.
You'll want to change this to a value
more appropriate for your use case before shipping your game.
For an image, you could use 4,096 times 4,096 times
4 since that's the largest texture many devices can
load into VRAM.
With that said, I can also verify
that the upload worked by going into the Firebase console,
finding the image, and inspecting it here.
I can also add the new download URLs and revoke old ones.
So if you do end up just sharing this out to the world,
you're not entirely out of luck if you wanted to take it back.
It just doesn't meet the strict security requirements
we have for something like an unguessable Firebase Dynamic
Link.
Remember, I'm not done yet.
I've created a reference and uploaded data,
but I still need to add metadata.
What is metadata anyway?
Metadata in the context of Firebase Cloud Storage
is just a set of textual string value pairs
that you can associate with a storage reference.
You can add metadata with a MetadataChange object.
There are some default pieces of metadata
to fill out, like I can say the content type is image/png
so that web browsers know how to handle this like an image.
You can also use the custom metadata field
to add a dictionary of whatever I'd like.
For instance, remember how my camera's rotating over time?
I can just get the world transform and the world
rotation of the camera and put it here.
That way, I might be able to set up
the scene to look like the one in the screenshot
on another machine.
Then I can add metadata just by modifying
my PutBytesAsync call.
So let me run the game one more time, capture a screenshot,
then run straight over to the Firebase console.
If I find this screenshot, I can see
that I now have all this super cool metadata.
And that's really it.
As I stated before, you definitely, definitely
want to enable security rules again,
especially if you're about to commit your project to GitHub
after watching this video.
And of course, a static screenshot
itself might not be as interesting
a viral marketing mechanic as I have led you to believe.
There are plugins that make it easy to generate animated GIFs
or movies, which would be way more entertaining content
to throw out onto social media.
And really, just any large pieces of binary data
will do, from replays to custom levels
to maybe machine learning data sets you've been using
to replicate your star players.
I'm not saying that you should do that,
but I don't want to be on record trying
to stop the singularity after the machines takeover.
I'd love to hear what you plan to make
or what you would like to hear me talk about next.
Either let me know in the comments
below or tweeting @pux0r3 on the Twitter.
See you next time.
[MUSIC PLAYING]