How to Upload a Unity Scnene to the Cloud
Using Firebase Cloud Storage with Unity to share user generated content
A few piece of cake steps to win the social media zeitgeist
If you've been post-obit the indie game scene for the past couple of years, y'all've seen really cool screenshots or even blithe gifs shared by fans of these games on various social media channels. As y'all may take seen in my previous post, I have (what I hope is) a super fun game written in Unity with sword chucks, bats and zombies that I'yard sharing for internal beta tests. I actually want to bulldoze upward the fizz and possibly get more testers in on this. I want some way to enable my game to go viral!
Thank you to Cloud Storage for Firebase, I have the perfect identify to go started. Let'due south take a look.
I will assume that you accept already set up Firebase and have the Unity SDK downloaded.
To review the game, I take armed my players with the coolest and most practical bodily existent life and totally not fabricated upwards weapon known to man: the sword chuck! When the player takes whatever impairment, obviously never self inflicted, I take a screenshot to allow for further study and honing of their strategy.
The obvious path forward is to let my players share their high scores, challenge each other, and hopefully go some excitement going about my game.
Configuring Cloud Storage
To kicking everything off, I need some place to actually share my high score. Fortunately, Firebase provides an SDK to aid y'all manage binary information in Google Cloud Storage which seems perfect for the screenshots I capture when you lose a round.
I chose the location nam5 since it'southward a multi-region location for the Us. This maximizes the availability and integrity of whatever data I persist to Deject Storage.
I'll leave the default rules in place for at present:
But what do they hateful?
Offset, this says that this is a rule for firebase.storage
. And so match /b/{bucket}/o
matches my storage bucket. For the most part, you should probably e'er leave these two lines in identify.
Then friction match /{allPaths=**}
matches annihilation in my bucket, storing the path in a variable named allPaths
.
allow read, write: if request.auth != naught
says that a histrion tin can read and write anywhere in allPaths
every bit long as they're authenticated via Firebase Hallmark. I'll cover the bare minimum for authentication in a moment, but I cover Firebase Authentication for Unity in a bit more item over in this video.
If yous want to dig in deeper, you can read near rules here.
Persisting User Data with Unity
I'll start the way I showtime anything in Unity, with a MonoBehaviour. I'll put the full text of this MonoBehaviour
at the end of the commodity for reference.
I designed the entry point of this MonoBehaviour
to exist the Trigger
office:
This is designed to be hooked upwardly in the Unity Editor in response to any UnityEvent. For now it's hooked up to a button click:
only I want to leave it open for more complex chains of events if needed in the time to come.
Trigger
checks to see if an upload is in progress with an _uploadCorotuine
field that I enshroud at the superlative of my file, and starts the actual upload in UploadData
. I'll somewhen set this to naught
when the coroutine is over.
Looking at UploadData
. I start out with some logic to pull the DeathData
out of _gameOverPanel
:
Don't worry also much well-nigh DeathData
. For the purposes of the game over scene, it but exposes a few public properties.
There are 2 things that I want to highlight here:
Kickoff, HandleException
is my general abstraction for failing out of this coroutine, yous'll see this again.
Second, I brand judicious employ of yield break;
to exit early on from a coroutine.
If y'all remember from the top of this post, my rules crave that a player must be logged in to be able to read or write information. For now, I'll create an anonymous user business relationship to satisfy this requirement — that is I'll register this installation of a game as a user rather than more than sophisticated credentials:
It's useful to note two primal patterns in this block of lawmaking:
First, I check auth.CurrentUser
to brand sure I'm not already signed in. This is cached locally and persists between runs, and then if I didn't do this SignInAnonymouslyAsync
would create a user account every time I uploaded a score! If y'all'd similar to know more about hallmark, meet this video.
Second, notation the yield return new WaitUntil(() => signInTask.IsCompleted);
. WaitUntil is a convenience class that lets yous plough whatever small snippet of logic into a coroutine. In this instance, I'm using it to wait for a Task
'south IsCompleted holding to go to true
. If y'all want to larn all about different strategies to work with threads in Unity, check out my previous web log post on the subject!
After I have a user signed in, I need to decide where I'thou going to store the images they upload. For this, I define:
Which is how I indicate that scores volition go under /finalScores/{UserID}/finalScore.png
, restricting each user to ane score. For at present this means that each user will simply e'er have one final score posted at a time, which I'll want to expand upon in the future.
I apply FirebaseStorage.GetReference
to get a reference to this path and cord.Format
to use C#'s own in-built string substitution library to substitute in the user id:
With all of this in place, I can write:
to upload my screenshot to Cloud Storage.
Augmenting my screenshots
A picture is worth a thousand words, but the words I desire are, "that the player scored xx points over 30 seconds before being struck by their own sword chuck." Your first consideration might be to store every score in a Realtime Database with the associated textual metadata then referencing a cloud storage node. This is actually how Mecha Hamster stores replays associated with high scores for its leaderboard.
Since I don't have a leaderboard for the Swordchuck Challenge and I don't desire to maintain two databases with the added complexity of keeping them in sync, I'd adopt to somehow comment a screenshot uploaded to Cloud Storage with the context under which the image was captured. Information technology turns out that that's a key feature of Cloud Storage called "Custom Metadata".
I can update my call to PutBytesAsync with a Dictionary<string,string>
, to add my relevant metadata direct to the epitome I upload:
After hooking this all into Unity, I tin observe an uploaded finalScore.png in my Storage console:
As well equally its associated custom metadata:
I won't cover retrieving data from Cloud Storage in detail in this post, simply information technology is a simple telephone call to GetMetadataAsync to pull the metadata back out.
Now I'm gear up to share my paradigm on social media. I can see a "Download URL" also associated with my image:
Which I could also get with a call to GetDownloadUrlAsync, assuming that the user is logged in.
Securing my Player Data
For this game correct now, I'll make any high scores a player shares entirely public to bulldoze interest into my game. I also don't want one user to be able to supercede another'southward high score. The current rules don't satisfy either of these requirements.
I'll go back over my Storage Rules tab:
and drop in this logic:
My biggest change is to replace lucifer /{allPaths=**} {
with match /finalScores/{userId}/finalScore.png {
. By doing this, I effectively lock abroad every other possible path in Cloud Storage.
I've also cleaved apart the rules a little flake. By saying permit read;
, I say that anyone tin can read what's in this path. This fashion, friends tin can share their scores on social media without requiring their friends to have accounts in my game.
allow write: if request.auth.uid = userId;
is where this gets cool. If you await at my friction match argument again, I pull out /finalScores/{userId}/finalScore.png
. This means that whatever happens to be between /finalScores/
and /finalScore.png
will get the variable userId
.
And then, I check if this userId
value matches request.auth.uid
. This variable is automatically populated with your Firebase Authentication user id if you're logged in and using the Unity SDKs for Auth and Storage. Unless a malicious player physically has admission to your phone, it won't be possible for them to overwrite your current finalScore.png
. Note that since I'm currently using bearding authentication, it won't be possible for a player to remove their own score either after uninstalling the game (although you can practice information technology manually every bit a developer in the console or with the admin SDK).
Every bit presently as you hit "Publish", these rules should exist live. Earlier doing that, let'due south exam these out a little. Click the Simulator button side by side to your rules, and let'south walk through some basic tests.
Beginning, I'll endeavour to go a concluding score anonymously by setting:
- "Simulation type" to "go"
- "Location" to
/finalScores/32/finalScore.png
- "Authenticated" to off
When I hit "Run", I get a green banner saying "Simulated read allowed".
Now, I'll effort to access any location that isn't represented in my rules file changing "Location" to /topSecret
:
When I press "Run", the banner turns red and indicates "Simulated read denied".
Next, I'll try to create a finalScore.png
every bit an anonymous user by setting:
- "Simulation blazon" to "create"
- "Location" to
/finalScores/32/finalScore.png
Note that Authentication is all the same set to fake.
Fifty-fifty though the banner says "Cipher value mistake." rather than "Imitation write denied", this rule is functioning equally expected. This volition be reported as any other access denied message when you access information technology from a client, merely in the simulator you lot're getting a picayune more information about why (in this case, request.auth
is goose egg
then you lot can't call .uid
).
Finally, permit's try writing a value with a uid that matches the design I wrote above by setting:
- "Authenticated" to true
- "Provider" to google.com
- "Firebase UID" to
32
(matching the32
in "Location")
And I get a green banner!
Everything looks fine! I can click publish, and I only need to work out how to share!
Wrap Up
So at present, I tin can take a screenshot whenever a sword chuck addict dies. Then I annotate this with some metadata about how well they did up until that indicate and how they died. Finally I created a nice social media friendly link that I can share with my friends.
My adjacent steps would be to be able to use this Cloud Storage information to provide a customized landing page for each high score that a user shared. I could do this with Firebase Hosting, or even by deep linking into the game on either Android or iOS. This could also be the start of a challenge system, perhaps by also sending a random seed up with the loftier scores.
I could as well really implement the SDKs for the social media platforms I plan on sharing my game to. I could fifty-fifty use their native SDKs as hallmark providers in Firebase Hallmark, too removing the reliance on anonymous hallmark. Keep an middle out for my next commodity as I expand more than on this game!
Appendix
See the entire UploadGameData.cs
Source: https://medium.com/firebase-developers/using-firebase-cloud-storage-with-unity-to-share-user-generated-content-25136cd3771d