How to write a C# backend for a Unity game using Firebase and Google’s Cloud Run

Patrick Martin
Firebase Developers
8 min readJun 4, 2019

--

Using Firebase with Unity for mobile development has overall been a super easy integration for me, but Cloud Functions have always been a little more challenging. Cloud functions alone are easy to work with, but I’m not as comfortable in JavaScript as I am in languages like C#. I also often need to do server-side validation of player actions, and if I can duplicate game logic on the client and server without rewriting it, I can greatly reduce my potential bug surface. Fundamental differences in each language, such as how they each handle numbers, can cause non-intuitive bugs to crop up in some of the least expected places.

Google Cloud recently launched Cloud Run as a way for you to deploy stateless containers as a cloud backend. Given that these are built on top of Kubernetes, you can use any combination of frameworks and languages you want (as long as you can Dockerize it). You just need to listen to the port specified by the $PORT environment variable and, for our purposes, you need to respond within 60 seconds.

Since I get my run of languages, I immediately decided to try to get C# running so I can use the same language in Unity and my backend. After some research, I decided to settle on .NET Core and Kestrel for my setup.

Project Setup

As of this writing, Cloud Run is beta. After installing the Cloud SDK, you’ll have to install the beta components by typing:

gcloud components install beta
gcloud components update

If you have an existing Firebase project, you should have a corresponding cloud project at console.cloud.google.com by default. If you don’t see your project under “Recent”, try checking the “All” tab or just search for it by name.

You can now add Cloud Run to your project by selecting it in the side menu and clicking “Start Using Cloud Run”. You will need billing enabled, but the console will walk you through turning that on if you haven’t done so already.

Now we’re ready to start setting up your project! Move to an empty directory where you want to work and type:

gcloud init

to make sure you’re using your account and to select your cloud project.

Once you have your gcloud account setup, you should create a quick project to upload. Since you’re going to be using .NET Core, you should install the corresponding SDK on your computer. Now, cd into an empty directory then type into your console:

dotnet new web

to create a project and:

dotnet run

to verify that it works. If you open your browser to the port indicated, you should see “Hello World.”

Container Time!

It’s time to get this ready for use in Knative now. The generated code is nearly ready, we just need to read the $PORT environment variable for this all to work. To do this, change:

to:

Now that your web app is ready to work with Knative, you need to create a Dockerfile to containerize it. Create a file in your project directory named Dockerfile. Then type the following:

Change "Backend.dll" to the name of your project’s artifact. If you’re unsure what that is, it should match the name of your csproj file.

I’ll break this down a little bit. This uses Microsoft’s provided dotnet build environment and runtime with:

FROM microsoft/dotnet:sdk AS build-env

and:

FROM microsoft/dotnet:aspnetcore-runtime

In the build-env, the first COPY instruction and dotnet restore make sure you have all the proper dependencies. Then we COPY in our entire project and dotnet publish it. Finally you copy the /out directory into the runtime environment and execute the command dotnet Backend.dll when it’s time for your container to run.

If you’ve done everything correct up until this point, you will be able to use gcloud to build your project by typing:

gcloud builds submit --tag gcr.io/$PROJECT_NAME/$CONTAINER_NAME

In my case, my $PROJECT_NAME is flappyfirebird and I want my Cloud Run container to be named backend. So I typed:

gcloud builds submit --tag gcr.io/flappyfirebird/backend

If you don’t see any errors, it’s time to upload the built container! Type:

gcloud beta run deploy --image gcr.io/$PROJECT_NAME/$CONTAINER_NAME

so, again, my command is:

gcloud beta run deploy --image gcr.io/flappyfirebird/backend

Right now the only available region is us-central1, so don’t be worried if you see that as your only region to deploy to.

When asked for your service name, enter whatever you want. I left mine as the default.

When asked to allow unauthenticated invocations, answer y for yes (this is NOT the default).

Allow unauthenticated invocations to new service [backend]?(y/N)?  y

It should take a little while, but you should eventually see the text Done, and a link to your service. Test it out by following the URL provided:

https://backend-jrddlzrh5q-uc.a.run.app

Firebase Hosting

Congratulations, you’re now running C# in a Cloud Run container! Now it’s time to setup Firebase Hosting.

Navigate over to the Firebase Console, and select your project. If you created your project in the Google Cloud Console first, you’ll have to click “Add project” and select the corresponding project from the dropdown.

Your project must be the Blaze “pay as you go” plan since you enabled billing in the Google Cloud project.

Now that you have a project setup, open your terminal and navigate to an empty directory for your Firebase project. Make sure that you have the command line tools installed, then type the command:

firebase init

Use this menu to initialize Hosting, and optionally any other Firebase products you want to use:

After selecting the Firebase project you’re configuring, you can leave everything else as the defaults. You can type:

firebase deploy

to make sure everything is working.

The final piece of this puzzle is to point some paths over to our Cloud Run instance. Open up firebase.json and add a new entry under "hosting" called "rewrites". I’ll start by routing "/helloworld" to my Cloud Run instance:

✔  Deploy complete!Project Console: https://console.firebase.google.com/project/flappyfirebird/overviewHosting URL: https://flappyfirebird.web.app

If you execute firebase deploy, you can verify that it’s working by appending /helloworld to the provided URL. In my case, this means that I type https://flappyfirebird.web.app/helloworld into my address bar.

Unity Integration

For bonus points, I’ll quickly integrate all of this into Unity. I wrote a really quick test MonoBehaviour called TestCloudRun.cs:

As expected, it logged:

Response: Hello World!UnityEngine.Debug:Log(Object)<RunTest>d__1:MoveNext() (at Assets/Scripts/TestCloudRun.cs:18)UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)

Adding Custom Logic

Now that we have code that we can read in Unity, let’s make something a little bit more dynamic. Rather than a simple “helloworld”, let’s log the time you hit the server. To get started, we need to add a new route to our server. Add a map to the endpoint "/time"to point to a HandleTime function:

Then implement a function that just returns the current time:

You can test this with dotnet run, going to localhost:5000/time. Then just build and deploy with gcloud builds submit and gcloud beta run. Note that you still can’t access it by going to /time on your Firebase Hosting site like you can with /helloworld.

We can open firebase.json and add /time as another path to our Cloud Run container, but instead we’ll use a wildcard to catch all requests and direct them:

Although this may look like I’m redirecting all traffic to my Cloud Run container, thus mostly negating the use of Firebase Hosting, this isn’t as all-encompassing as you may think. Notably:

  1. Static assets always take precedence over rewrites, so you’ll still be able to get to your index.html.
  2. Rewrites are applied in order. If you had a few that went to a different container or a Cloud Function, they’ll be hit instead of ”/*” if they match an incoming request first.

If you go to your site’s time endpoint (for me this was https://flappyfirebird.web.app/time), you should see the current server time! If you refresh the page you see… the same time as before. Why is this? If you run curl -vs https://flappyfirebird.web.app/time you’ll see the entry cache-control: max-age=3600. This means that you’ll have to wait an hour before seeing a new time!

Controlling Firebase’s Content Delivery Network

Firebase Hosting will cache whatever you put in your cache control header, so let’s do that for our time endpoint. Kestrel has the concept of Middleware, which is a system with which you can augment or modify the response for an incoming request. You can modify how long Firebase caches the response to a user’s request by modifying the CacheControl header with a simple piece of custom middleware (which I’ve listed below). I opted to put this in the HandleTime function, since I don’t mind caching the text “Hello World” for an hour, but where you put it is up to the needs of your service.

Where do you go from here?

My goal is to build a simple cloud backend for a small Unity game I’m building, but you might have other needs. To tie into other Firebase services, you should look into the C# admin SDK and the Firebase REST API. There are also a number of excellent open source community projects available on NuGet to bridge the gap between the official admin SDK and REST. Note that even though we provide a Unity C# SDK, this is intended for clients and will probably not work in your server environment. The stable client SDKs are only fully implemented in iOS and Android, where it relies on native features of each OS to improve the end user experience.

--

--

Patrick Martin
Firebase Developers

I’ve been a software engineer on everything from games to connected toys. I’m now a developer advocate for Google’s Firebase.