WebGL App Setup

Setting up an <iframe>

This section will give an example with some references on how to get started with your Unity WebGL build and messaging between javascript and your unity instance. We will be using a combination of , React & Next.js in the following example, but these steps should be applicable to a number of different environments.

Getting Started

The first step is to create your WebGL build in Unity, if unfamiliar with Unity, you can view more details in Deploying Your Unity WebGL Game Through Sample App.

The default WebGL build template in your Unity Player Settings, which we will be using for this example, will provide the following files:

  • Build (folder) -- this includes the important bits for running unity in your browser.

  • TemplateData (folder) -- this includes asset and styles for displaying your Unity environment and compliments the provided index.html

  • index.html -- this is a web template which will help you get started by loading Unity into the page. We will edit this later.

The index.html and TemplateData aren't mandatory, but we will be using them in this example. They only need to be added once and then updated, whereas the files in Build should be updated with each new build. Now that we have these files, we will add them into a subfolder called "unity" within our public folder.

Now that we have the files loadable over url from our public folder, we already have enough to load up our project. We might need to edit the buildUrl and filenames in the config to point to the correct location of the freshly added build files. In this example, we have set the build name as simply "WebGL" so your file names and paths will likely differ. We are also omitting the extra properties from the following snippet, but you're welcome to keep them.

var buildUrl = "Build";
var loaderUrl = buildUrl + "/WebGL.loader.js";
var config = {
  dataUrl: buildUrl + "/WebGL.data",
  frameworkUrl: buildUrl + "/WebGL.framework.js",
  codeUrl: buildUrl + "/WebGL.wasm",
};

At this point, you should be able to run your build by navigating to /unity/index.html. There are a number of items on this page that aren't needed for this example, so for now we will remove them. We will also be updating the style of the canvas to fill available width and height.

Our index.html should look like the following:

<!DOCTYPE html>
<html lang="en-us">
  <body>
    <div id="unity-container" style="width: 100%; height: 100%; margin: 0; overflow: hidden;">
      <canvas id="unity-canvas" style="width: 100%; height: 100%;"></canvas>
    </div>
    <script>
      var canvas = document.querySelector("#unity-canvas");
      var buildUrl = "Build";
      var loaderUrl = buildUrl + "/WebGL.loader.js";
      var config = {
        dataUrl: buildUrl + "/WebGL.data",
        frameworkUrl: buildUrl + "/WebGL.framework.js",
        codeUrl: buildUrl + "/WebGL.wasm",
      };

      var script = document.createElement("script");
      script.src = loaderUrl;
      script.onload = () => {
        createUnityInstance(canvas, config)
          .then((unityInstance) => {

          })
          .catch((message) => {
            alert(message);
          });
      };

      document.body.appendChild(script);
    </script>
  </body>
</html>

Sending a Message to Unity

After our unity instance has loaded, we will need to store a reference to it in our main script.

let unityWebGLInstace;

...
  createUnityInstance(canvas, config)
    .then((unityInstance) => {
      unityWebGLInstace = unityInstance;
    })
...

Now that we have a reference to the unity instance, we can add a handler to our main script that will be able to pass the did token.

function SetDIDToken(token) {
  if (!unityWebGLInstance) return;

  unityWebGLInstance.SendMessage("LoginReceiver", "SetDIDToken", token);
}

With this, we have the core of what we need to send messages between javascript and our unity environment.

React <iframe> Setup

We will continue further into this example by setting up a React component that will display this content within an iframe. This requires another layer of messaging handled via event listeners within the page. We now need to add the following to our main script, which will listen for events sent to the iframe.

window.addEventListener("message", function(event) {
  if (event.data && event.data.type === "SetDIDToken") {
    SetDIDToken(event.data.value);
  }
});

At this point, we'll need to create a new react component so that we can add our iframe. We need to give the iframe an id and point the src to the index.html that we put in our public folder.

export const UnityComponent = () => {

  return (
    <iframe
      id="unity-webgl-iframe"
      src="unity/index.html"
      style={{
        position: "relative",
        display: "flex",
        top: 0,
        left: 0,
        zIndex: 999,
        width: "100vw",
        height: "100vh",
        border: "none",
      }}
      title="Unity WebGL"
      allowFullScreen
    />
  );
}

We then need to define a handler that can get the iframe and then send it the did token.

function setDidToken(token) {
  const iframe = document.getElementById("unity-webgl-iframe");
  if (iframe && iframe.contentWindow) {
    iframe.contentWindow.postMessage(
      {
        type: "SetDIDToken",
        value: token,
      },
      "*"
    );
  }
}

Now lets add the event listener to our component with a useEffect.

import { useEffect } from "react";

...

useEffect(() => {
  const iframe = document.getElementById("unity-webgl-iframe");

  function handleUnityMessage(event) {
    if (event.data === "unity-webgl-loaded") {
      setDidToken(assetlayer.didToken);
    }
  }

  iframe.contentWindow.addEventListener("message", handleUnityMessage);

  return () => {
    iframe.contentWindow.removeEventListener("message", handleUnityMessage);
  };
}, []);

At this point we have an event listener that will listen for the iframe's 'unity-webgl-loaded' event, then pass the didtoken from our assetlayer instance back to the iframe. However, our iframe isn't yet sending the onload event, let's fix that by adding the following after we set the reference to the unity instance -- after having loaded it.

this.postMessage("unity-webgl-loaded", "*")

Now we should have everything we need to fully integrate our unity build into our react app.

Our index.html should look like the following:

<!DOCTYPE html>
<html lang="en-us">
  <body>
    <div id="unity-container" style="width: 100%; height: 100%; margin: 0; overflow: hidden;">
      <canvas id="unity-canvas" style="width: 100%; height: 100%;"></canvas>
    </div>
    <script>
      var canvas = document.querySelector("#unity-canvas");
      var buildUrl = "Build";
      var loaderUrl = buildUrl + "/WebGL.loader.js";
      var config = {
        dataUrl: buildUrl + "/WebGL.data",
        frameworkUrl: buildUrl + "/WebGL.framework.js",
        codeUrl: buildUrl + "/WebGL.wasm",
        streamingAssetsUrl: "StreamingAssets",
      };
      var unityWebGLInstance;

      function SetDIDToken(token) {
        if (!unityWebGLInstance) return;

        unityWebGLInstance.SendMessage("LoginReceiver", "SetDIDToken", token);
      }

      var script = document.createElement("script");
      script.src = loaderUrl;
      script.onload = () => {
        createUnityInstance(canvas, config)
          .then((unityInstance) => {
            unityWebGLInstance = unityInstance;

            this.postMessage("unity-webgl-loaded", "*")
          })
          .catch((message) => {
            alert(message);
          });
      };

      document.body.appendChild(script);
      window.addEventListener("message", function(event) {
        if (event.data && event.data.type === "SetDIDToken") {
          SetDIDToken(event.data.value);
        }
      });
    </script>
  </body>
</html>

And our new react component should look like the following:

import React, { useEffect } from "react";

function sendTokenToUnity(token) {
  const iframe = document.getElementById("unity-webgl-iframe");
  if (iframe && iframe.contentWindow) {
    iframe.contentWindow.postMessage(
      {
        type: "SetDIDToken",
        value: token,
      },
      "*"
    );
  }
}

export const UnityComponent = () => {

  useEffect(() => {
    const iframe = document.getElementById("unity-webgl-iframe");

    function handleUnityMessage(event) {
      if (event.data === "unity-webgl-loaded") {
        sendTokenToUnity(assetlayer.didToken);
      }
    }

    iframe.contentWindow.addEventListener("message", handleUnityMessage);

    return () => {
      iframe.contentWindow.removeEventListener("message", handleUnityMessage);
    };
  }, []);

  return (
    <iframe
      id="unity-webgl-iframe"
      src="unity/index.html"
      style={{
        position: "relative",
        display: "flex",
        top: 0,
        left: 0,
        zIndex: 999,
        width: "100vw",
        height: "100vh",
        border: "none",
      }}
      title="Unity WebGL"
      allowFullScreen
    />
  );
};

Final Notes

As a final note, you may want to update the iframe with a width & height of "100%". Alternatively, if not using the iframe, you may need to update the unity-container with a width & height of 100vw & 100vh. There just needs to be a single parent with a defined width and height for the canvas to properly fill the available space. You may also want to restore some of the removed content, like the loading bar.

You can view more detailed examples & implementations within our sample-app Getting Started With Sample App Locally;

Unity Game Integration Example;

index.html: https://github.com/unbounded-enterprise/sample-app/blob/main/public/unity/index.html

React Component: https://github.com/unbounded-enterprise/sample-app/blob/main/src/components/play-unity.js

Unity Model Viewer Example;

index.html: https://github.com/unbounded-enterprise/sample-app/blob/main/public/unity/Viewer/index.html

React Component: https://github.com/unbounded-enterprise/sample-app/blob/main/src/components/widgets/unity/unity-model-viewer.js

Last updated