How to create a UI in Unity using HTML

In this article, I show how surprisingly easy it is to create a UI in Unity using standard HTML, CSS, and JavaScript. If you want to jump to a specific section, you can use the following links:

Why HTML?

Unity has its own proprietary UI system, so why would we want to use HTML? Here are just some of the advantages:

  • You can use the plethora of existing web UI components (like date pickers, charts, Google Maps embed).
  • Rapid development using state-of-the-art front-end tooling (like browser DevTools, hot-reloading, and frameworks like React.js).
  • It's easier to create a responsive design that changes based on screen size (for example, using flexbox).
  • Serving parts of your UI from a web server allows you to update it without updating the entire app.
  • You can leverage your company's web development skills and existing web designs.
  • HTML and CSS are more powerful and flexible than Unity's UI system.

As wonderful as HTML is, there is a tradeoff, which is that embedding a browser does utilize more memory and CPU than just using Unity's built-in UI system. So, it's largely a tradeoff between development speed versus runtime performance. Being able to develop faster often makes this tradeoff worthwhile, however if your application requires the highest possible performance, you may want to stick with Unity's built-in UI system.

Tools needed

To display HTML, we're going to need a browser. And there's no better browser for Unity than 3D WebView. Here's why:

  • Seamless cross-platform support across most devices (Windows, macOS, Android, iOS, UWP, and more to come).
  • Best-in-class documentation, product quality, and support. These are areas where 3D WebView really blows other solutions out of the water.
  • 3D WebView renders web content to textures that can be displayed anywhere in Unity scenes. In contrast, alternative products usually work by floating a native 2D webview over the Unity game view, which doesn't work for 3D or VR.

Overview of approach

Here's our high-level plan for creating the UI:

Implementation

For this example, I decided to create a simple signin UI that asks the user for their credentials, sends an authorization request to an API, and sends the resulting auth token to the app's C# code. You can see the completed project here on GitHub.

  1. I started by creating a new Unity project.

Creating a new Unity project

  1. Next, I installed 3D WebView for Windows and macOS from the Asset Store.

3D WebView in the Asset Store

  1. I then created a new scene named UnityHtmlUiExample with a CanvasWebViewPrefab configured to occupy the entire screen. I really just copied and pasted 3D WebView's CanvasWebViewDemo scene, but it's also easy just to add a Canvas to a new scene and drag a CanvasWebViewPrefab into it.

UnityHtmlUiExample scene in the Unity editor

  1. After that, I added a new Assets/StreamingAssets directory for the HTML and CSS files to go into.

StreamingAssets in the Unity editor

  1. Then, I implemented the HTML and CSS for a simple signin UI and placed those files into the StreamingAssets directory. Below is the HTML from signin.html. To see the CSS, check out the files on GitHub.
<!DOCTYPE html>
<html>
  <head>
    <!-- Make the background transparent (https://support.vuplex.com/articles/how-to-make-a-webview-transparent) -->
    <meta name="transparent" content="true">
    <link href="./styles.css" rel="stylesheet">
  </head>
  <body>
    <div class="signin">
      <div>
        <h1>Welcome!</h1>
        <p>
          Please sign in to get started.
        </p>
        <input id="email" type="email" placeholder="Email">
        <input id="password" type="password" placeholder="Password">
        <span id="error">The email or password is incorrect.</span>
        <button disabled>
          <span>Sign in</span>
          <div class="spinner">
            <img src="spinner.svg">
          </div>
        </button>
        <a>Sign in later</a>
        <div id="success-message">
          <div>
            <h1><span>✓</span> You're logged in</h1>
          </div>
        </div>
      </div>
    </div>
    <script>
      // Get references to the interactive elements
      // so that we can script their behavior.
      const emailInput = document.getElementById('email');
      const passwordInput = document.getElementById('password');
      const submitButton = document.getElementsByTagName('button')[0];
      const errorMessage = document.getElementById('error');
      const successMessage = document.getElementById('success-message');
      const signInLaterLink = document.getElementsByTagName('a')[0];

      // Attach the submit button click handler.
      submitButton.onclick = async () => {
        // Disable the controls while making the authentication request.
        submitButton.setAttribute('disabled', true);
        submitButton.classList.add('loading');
        emailInput.setAttribute('disabled', true);
        passwordInput.setAttribute('disabled', true);

        let authToken = null;
        try {
          // Make an HTTP request to a mock authentication API that
          // returns an auth token if the email address is valid and
          // the password is "password".
          const response = await fetch('https://api.vuplex.com/examples/fake-auth', {
            method: 'post',
            body: JSON.stringify({
              email: emailInput.value,
              password: passwordInput.value
            })
          });
          if (response.ok) {
            const body = await response.json();
            authToken = body.authToken;
          }
        } catch (error) {
          console.log('Unexpected error occurred during authentication: ' + error);
        }

        if (authToken) {
          // Fade out the UI to transparent
          successMessage.style.display = 'flex';
          document.body.style.opacity = 0;
          // Post a message to the C# code to send the auth token (https://support.vuplex.com/articles/how-to-send-messages-from-javascript-to-c-sharp).
          vuplex.postMessage('auth_success:' + authToken);
        } else {
          // Authentication failed, so show an error message.
          errorMessage.style.display = 'block';
          submitButton.removeAttribute('disabled');
          emailInput.removeAttribute('disabled');
          passwordInput.removeAttribute('disabled');
          submitButton.classList.remove('loading');
        }
      };

      // Attach the click handler for the "Sign in later" link.
      signInLaterLink.onclick = () => {
        // Fade out the UI to transparent
        document.body.style.transitionDuration = '0.5s';
        document.body.style.opacity = 0;
        // Post a message to the C# code to indicate auth was skipped.
        vuplex.postMessage('auth_skipped');
      };

      // Attach handlers for form validation.
      emailInput.oninput = enableOrDisableSubmitButton;
      passwordInput.oninput = enableOrDisableSubmitButton;

      function enableOrDisableSubmitButton() {
        // Only enable the submit button if both the
        // email and password are filled out.
        errorMessage.style.display = 'none';
        const submitButtonEnabled = !submitButton.getAttribute('disabled');
        const emailIsValid = emailInput.value.trim().length;
        const passwordIsValid = passwordInput.value.trim().length;
        const emailAndPasswordAreValid =  emailIsValid && passwordIsValid;
        if (submitButtonEnabled !== emailAndPasswordAreValid) {
          if (emailAndPasswordAreValid) {
            submitButton.removeAttribute('disabled');
          } else {
            submitButton.setAttribute('disabled', true);
          }
        }
      }
    </script>
  </body>
</html>
  1. In the Unity editor, I then set the CanvasWebViewPrefab's Initial URL field to streaming-assets://signin.html so that it loads the page.

Initial URL field in the Unity editor

  1. Finally, I hit play to see the UI in action:

I also installed 3D WebView for Android and tested on Android:

Closing thoughts

Although I kept this example simple, it's easy to think of valuable improvements that we could add. For example, we could modify the signin page to give users the option to signin with Google or Facebook. Or we could implement the UI in React.js and share UI components across multiple views. I plan to write articles about both of these topics in the future. If you're interested in creating your own Unity UIs with HTML, go get 3D WebView and let me know if you have questions or feedback!