Communicating to and from Unity3d to Javascript -- Part 2

Communicating to and from Unity3d to Javascript -- Part 2

Hooking up Unity3d to Javascript

First step, is to make sure that your Unity3d project is set to build to WebGL.

You want to go to File > Build Settings in the top menu. Make sure WebGL is selected and make sure to click Development Build. This will give you some extra debugging options. When you are ready to run this production, you would uncheck this box.

Build Settings To Export Your Unity3d Program To WebGL

Next thing, you are going to do is create a reference to an Javascript function that you can call from the C# scripts that interact with Unity GameObjects.

The JSlib file

Unity3d uses Emscripten as the underlying tool to compile down to web assembly. The JSlib file is where you will let Unity3d know about the JS functions that should be exposed to the C# scripts.

The rule about these files is that they must be in a folder called Plugins that is inside of the Assets folder.

The inside of the JSlib should be the following:

mergeInto(LibraryManager.library, {
    // declare functions here

})

What we are going to do is reference a function on the global window object that is available to the JS running in the browser. Don't worry about triggering any sort of error here because you haven't defined a window variable. The build will reference the correct version of window when the code is running in a browser.

mergeInto(LibraryManager.library, {
    sendToJS: function (str) {
        window.$vm.triggerJSAction(Pointer_stringify(str));
    }
});

The $vm will reference a variable that is enabled in our VueJS app on the HTML page.

This sendToJS function takes one parameter that will be turned into a JS String object once it triggers the function that will be on our VueJS app. There are only a few data types that you can pass between C# and JS. The Pointer_stringify function is builtin and will change the data into a string.

To be able to call this sendToJS function in your C# scripts, you have add the following declaration.

External functions

Create a new C# script. Underneath, the class declaration, place the following:

#if UNITY_WEBGL
    [DllImport("__Internal")] 
    private static extern void sendToJS(string msg);
#else
    private static void sendToJS(string msg)
    {
        // maybe some replacement code for other build targets or just an empty method
        Debug.Log("would be getting the following sent to browser");
        Debug.Log(msg);
    }
#endif

This tells your C# script to load an DLL and references the function that you made in the JSlib file. Make sure that the functions have the same signature.

The parts of code with the hashmarks in front of them are preprocessing directives. This basically tells C# to only load in the external DLL when the compiling for an WebGL build.

Therefore an example C# script would be the following:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.InteropServices;

    public class CommsScript : MonoBehaviour
    {
        #if UNITY_WEBGL
            [DllImport("__Internal")] 
            private static extern void sendToJS(string msg);
        #else
            private static void sendToJS(string msg)
            {
                // maybe some replacement code for other build targets or just an empty method
                Debug.Log("would be getting the following sent to browser");
                Debug.Log(msg);
            }
        #endif


        // Start is called before the first frame update
        void Start()
        {
            
        }

        // Update is called once per frame
        void Update()
        {
            
        }
    }
}

Now all you have to do is attach this new script to a GameObject in your Unity3d scene. I'm using the GameCreator module in this example, and this particular framework comes with objects called triggers (which is basically a box collidor) that hook up actions. It is in this space where I put that I want to invoke the commWrapper method, which in turn calls the sendToJS method.

Unity3d with GameCreator modules loaded
Unity3d after attaching the CommWrapper method to an Action

Enter Vue.JS

The final part of this excercise is to setup the HTML file to handle communication emitting from Unity3d.

I used VueJS as my frontend framework because we don't mess with Facebook products around here.

When you do a WebGL build for a Unity3d project, it will output an HTML file, a Build folder, and TemplateData folder. The Build folder is the most important part of this, as this contains the loader script and the wasm file that actually contains the Unity3d scene.

Copy that Build folder into where ever you made your Vue project. We want to make sure the Build folder is copied to where the static files are placed. If you used the Vue CLI to create your Vue project (which you should be), then you want to copy this to the public folder.

And finally, you want to add a script tag that links to back to the Build/game.loader.js file in the HTML file that Vue uses. The game.loader.js file has a function that boostraps the the rest of the game.

Now what usually happens on a Unity WebGL build is that a script tag is created and inserted into the HTML dynamically. The new script tag handles the loading/fullscreen toggling, etc. What we want to do is integrate that fully with the VueJS framework.

In your Vue component, let's add an element and put a reference on it. This will be where the visuals of the game will be shown.

Now let's add the code that will bootstrap Unity3d into Vue. Most of this code will take place in the mounted function.

mounted: function () {
    window.$vm.triggerJSAction = this.triggerJSAction;
    var canvas = this.$refs.unity_canvas;
      if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
        console.log('its on mobile!');
        return;
      } else {
        this.canvasStyle.width = "100%";
        this.canvasStyle.height = "100%";
      }
      var self = this;
      window.createUnityInstance(canvas, self.config, () => {
          self.isLoading = true;
        }).then((unityInstance) => {
          self.isLoading = false;
          self.unityInstance = unityInstance;
        }).catch((message) => {
          alert(message);
        });
  }

The createUnityInstance function is from the game.loader.js file that we linked in the HTML file earlier. This function returns a Promise that should resolve to a unityInstance object. This object represents the Unity3d scene on the browser. You can also send information from the browser to Unity3d via this object. By calling the sendMessage method. An example would be the following:

self.unityInstance.sendMessage('GameObject', 'myFunction', 'testValue');

You will notice that there is a self.config parameter that we pass into the createUnityInstance function. This is a javascript object that tells the function the location of the wasm and other build files. Let's go ahead and add this to the data section of our Vue component.

data: function () {
    return {
        loaderUrl: "Build/game.loader.js",
        config: {
          dataUrl: "Build/game.data",
          frameworkUrl: "Build/game.framework.js",
          codeUrl: "Build/game.wasm",
          streamingAssetsUrl: "StreamingAssets",
          companyName: "DefaultCompany",
          productName: "VRWebSite",
          productVersion: "0.1",
        }
      }
    }

After that we should have the frontend working well. We are able to send messages to the Unity3d program and Unity3d is able to send messages back to our frontend. Now let's add in the server side aspect.