4

My goal is to build a WebGL game in Unity that has a specific form and upon submission that form posts data into one of the Firebase storage solutions. After reading this article, it's clear to me I need to use Cloud Firestore instead of Realtime Database. Good news is that as of of March 2020, one of the team members wrote,

we released Firebase Unity SDK 6.12.0 which includes an alpha release of Firestore.

Thing is, Firebase's Unity SDK isn't for WebGL builds and, for someone going through that thought process, could use Firebase JS SDK (spam alert). From looking at the release notes, one can see that Firebase JS SDK supports Firestore and so this has all the conditions in place for a quick solution.

So, I've gone to Firebase console, created a project, a Web app to use Firebase JS SDK and this process gave as output the following code

<!-- The core Firebase JS SDK is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/7.17.1/firebase-app.js"></script>

<!-- TODO: Add SDKs for Firebase products that you want to use
     https://firebase.google.com/docs/web/setup#available-libraries -->
<script src="https://www.gstatic.com/firebasejs/7.17.1/firebase-analytics.js"></script>

<script>
  // Your web app's Firebase configuration
  var firebaseConfig = {
    apiKey: "",
    authDomain: "",
    databaseURL: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: "",
    measurementId: ""
  };
  // Initialize Firebase
  firebase.initializeApp(firebaseConfig);
  firebase.analytics();
</script>

with a message saying

Copy and paste these scripts into the bottom of your tag, but before you use any Firebase services

In addition to that, this is how to Call JavaScript functions from Unity scripts and this is a Cloud Firestore JS Sample App.

Given this information, how can then the form be created?

0

1 Answer 1

2

Let's say you want a form that receives as input

  • String
  • Number
  • Text from the user

In your Firestore console, create a collection and give it a name (like formDataTree), give an autoID and add the fields

  • strVal
  • intVal
  • webVal

Cloud Firestore create collection

Then, I would put those scripts at the bottom of the head tag in your WebGL template. So, create a folder in Assets named WebGLTemplates, and a folder named New Template (or whatever name you will) and add an index.html there.

New WebGL Template in Unity

According to the documentation, this index.html should be similar to

<!DOCTYPE html>
<html lang="en-us">

  <head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Unity WebGL Player | %UNITY_WEB_NAME%</title>
    <script src="%UNITY_WEBGL_LOADER_URL%"></script>
    <script>
    var unityInstance = UnityLoader.instantiate("unityContainer", "%UNITY_WEBGL_BUILD_URL%");
    </script>
  </head>
  
  <body>
    <div id="unityContainer" style="width: %UNITY_WIDTH%px; height: %UNITY_HEIGHT%px; margin: auto"></div>
  </body>
  
</html>

So, with this new information, it'll be something like this

<!DOCTYPE html>
<html lang="en-us">

  <head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Unity WebGL Player | %UNITY_WEB_NAME%</title>
    <script src="%UNITY_WEBGL_LOADER_URL%"></script>
    <script>
        var unityInstance = UnityLoader.instantiate("unityContainer", "%UNITY_WEBGL_BUILD_URL%");
    </script>
    <!-- The core Firebase JS SDK is always required and must be listed first -->
    <script src="https://www.gstatic.com/firebasejs/7.17.1/firebase-app.js"></script>

    <!-- TODO: Add SDKs for Firebase products that you want to use
         https://firebase.google.com/docs/web/setup#available-libraries -->
    <script src="https://www.gstatic.com/firebasejs/7.17.1/firebase-analytics.js"></script>

    <script>
      // Your web app's Firebase configuration
      var firebaseConfig = {
        apiKey: "",
        authDomain: "",
        databaseURL: "",
        projectId: "",
        storageBucket: "",
        messagingSenderId: "",
        appId: "",
        measurementId: ""
      };
      // Initialize Firebase
      firebase.initializeApp(firebaseConfig);
      firebase.analytics();
    </script>
  </head>
  
  <body>
    <div id="unityContainer" style="width: %UNITY_WIDTH%px; height: %UNITY_HEIGHT%px; margin: auto"></div>
  </body>
  
</html>

Then, under Player settings, select that template.

Player WebGL Template

Then in the body of the template, have a form with its visibility to hidden. Include things from the game as well as any inputs you want filled out in the browser:

<form id="webForm" style="visibility:hidden;">
    <input type="hidden" id="stringInput" name="stringInput">
    <input type="hidden" id="intInput" name="intInput">
    <label for="webInput">web input</label><input type="text" id="webInput" name="webInput">
    <button type="submit">Submit</button>
</form>

Then below the Firebase script and the form, include a submit listener for the form on the page which will submit it to Firestore (based on this answer):

myForm.addEventListener('submit', function(evt) {
  evt.preventDefault(); //Prevent the default form submit action

  var strVal = myForm.stringInput.value;
  var intVal = myForm.intInput.value;
  var webVal = intInput.webInput.value;

  var formData = {
    "strVal" : strVal,
    "intVal" : intVal,
    "webVal" : webVal
  };

  firebase.database().ref('/formDataTree').push( formData ); // Adds the new form data to the list under formDataTree node
});

All in all, the index.html should be something like this

<!DOCTYPE html>
<html lang="en-us">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Change Mapping | %UNITY_WEB_NAME%</title>
    <script src="%UNITY_WEBGL_LOADER_URL%"></script>
    <script>
        var unityInstance = UnityLoader.instantiate("unityContainer", "%UNITY_WEBGL_BUILD_URL%");
    </script>
    
    <!-- The core Firebase JS SDK is always required and must be listed first -->
    <script src="https://www.gstatic.com/firebasejs/7.17.1/firebase-app.js"></script>

    <!-- TODO: Add SDKs for Firebase products that you want to use
         https://firebase.google.com/docs/web/setup#available-libraries -->
    <script src="https://www.gstatic.com/firebasejs/7.17.1/firebase-analytics.js"></script>

    <script>
      // Your web app's Firebase configuration
      var firebaseConfig = {
        apiKey: "",
        authDomain: "",
        databaseURL: "",
        projectId: "",
        storageBucket: "",
        messagingSenderId: "",
        appId: "",
        measurementId: ""
      };
      // Initialize Firebase
      firebase.initializeApp(firebaseConfig);
      firebase.analytics();
    </script>
  </head>
  <body>
    <div style="height:20px; width: %UNITY_WIDTH%px; background: green;" onclick="unityInstance.SetFullscreen(1)"><b>Click here to make it full screen.</b></div>
    <div id="unityContainer" style="width: %UNITY_WIDTH%px; height: %UNITY_HEIGHT%px; margin: auto"></div>
    <form id="webForm" style="visibility:hidden;">
        <input type="hidden" id="stringInput" name="stringInput">
        <input type="hidden" id="intInput" name="intInput">
        <label for="webInput">web input</label><input type="text" id="webInput" name="webInput">
        <button type="submit">Submit</button>
    </form>
    <script>
        var myForm = document.getElementById("webForm");
        myForm.addEventListener('submit', function(evt) {
          evt.preventDefault(); //Prevent the default form submit action

          var strVal = myForm.stringInput.value;
          var intVal = myForm.intInput.value;
          var webVal = intInput.webInput.value;

          var formData = {
            "strVal" : strVal,
            "intVal" : intVal,
            "webVal" : webVal
          };

          firebase.database().ref('/formDataTree').push( formData ); // Adds the new form data to the list under formDataTree node
        });
    </script>
  </body>
</html>

Using the apiKey, authDomain, etc in the template it means that it will show up when inspecting the page or viewing page source as. Yet, as mentioned here, it's ok to share that information.

Then, in your Assets folder, create a Plugins folder and add to it a .jslib file, for instances named form.jslib, that has a function that shows the form, and puts game data into the form's hidden inputs.

Plugins in Asset

mergeInto(LibraryManager.library, {
  ShowWebForm: function (importantString, importantInt) {
    var myForm = document.getElementById("webForm");

    myForm.stringInput.value = Pointer_stringify(importantString);
    myForm.intInput.value = importantInt;

    myForm.style.visibility="visible"
   
  },
});

After the steps up to this point, if you get in the console net::ERR_BLOCKED_BY_CLIENT, simply deactivate AdBlocker for that site as that's why you get that error.

Finally, in Unity, declare that function and call it when appropriate. So, considering you have a newly created scene (so it only has Main Camera and Directional Light), you could call that method in some code that gets called in the new scene. It's a static method so as long as you can find the data you need for the parameters you can call it from anywhere.

[DllImport("__Internal")]
private static extern void ShowWebForm(string importantString, int importantInt);

public void Start()
{
    // Suppose we want to send the version of unity the app is running on 
    // and the unix timestamp at start
    string unityVersion = Application.unityVersion;

    System.DateTime epochStart = new System.DateTime(1970, 1, 1, 0, 0, 0,  
            System.DateTimeKind.Utc);
    int cur_time = (int)(System.DateTime.UtcNow - epochStart).TotalSeconds;

    ShowWebForm(unityVersion, cur_time);
}

Alternatively, if you don't want to have the user fill things out in the browser, you can leave the form invisible and instead of setting it to visible, dispatch a submit event:

mergeInto(LibraryManager.library, {
  ShowWebForm: function (importantString, importantInt) {
    var myForm = document.getElementById("webForm");

    myForm.stringInput.value = Pointer_stringify(importantString);
    myForm.intInput.value = importantInt;
    myForm.webInput.value = "some other value from the game could go here";

    myForm.dispatchEvent(new Event('submit'));
  },
});

Can't test at the moment, so be aware of typos or other syntax errors.

5
  • 1
    Would you be ok with having apiKey: "", authDomain: "", databaseURL: "", in that template? It will show up when inspecting the page or viewing page source. Commented Aug 7, 2020 at 12:03
  • 1
    Also, what do you mean with Finally, in Unity, declare that function and call it when appropriate. assuming we just created a new Scene (so it only has Main Camera and Directional Light)? Commented Aug 7, 2020 at 12:19
  • 2
    @TiagoMartinsPeres李大仁 hmm, I'm not familiar with firebase but I found this answer which makes it seem like these can be safely exposed. As for he new scene question, I would suppose you could call that method in some code that gets called in the new scene. It's a static method so as long as you can find the data you need for the parameters you can call it from anywhere. Thanks for improving my answer also!
    – Ruzihm
    Commented Aug 7, 2020 at 14:28
  • 2
    Thank you for your help up to this point. The answer is getting very good and I haven't seen such thing up to this point yet anywhere. Still, I'm not visualizing the last part Ruzihm. If you will, a more specific case of its usage would help to improve the answer. Commented Aug 7, 2020 at 19:05
  • 2
    @TiagoMartinsPeres李大仁 Thank you again for the improvements you are contributing. I hope my latest edit helps.
    – Ruzihm
    Commented Aug 7, 2020 at 19:18

Not the answer you're looking for? Browse other questions tagged or ask your own question.