8

I'm looking to integrate a Unity WebGL-project into an Angular2 app. What's the proper way to move all this script into an Angular2 component?

First, the Unity WebGL exports an index.html 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 | Espoo web manager (Prefab preview)</title>
    <link rel="shortcut icon" href="TemplateData/favicon.ico">
    <link rel="stylesheet" href="TemplateData/style.css">
    <script src="TemplateData/UnityProgress.js"></script>  
    <script src="Build/UnityLoader.js"></script>
    <script>
      var gameInstance = UnityLoader.instantiate("gameContainer", "Build/builds.json", {onProgress: UnityProgress});
    </script>
  </head>
  <body>
    <div class="webgl-content">
      <div id="gameContainer" style="width: 960px; height: 600px"></div>
      <div class="footer">
        <div class="webgl-logo"></div>
        <div class="fullscreen" onclick="gameInstance.SetFullscreen(1)"></div>
        <div class="title">Espoo web manager (Prefab preview)</div>
      </div>
    </div>
  </body>
</html>

I started to split this and first I moved the stylesheet into the template .css file:

@import './webgl-app/TemplateData/style.css';

Then I moved the javascript into the component .ts-file:

import { Component, AfterViewInit } from '@angular/core';
import './webgl-app/TemplateData/UnityProgress.js';
import './webgl-app/Build/UnityLoader.js';

declare var UnityLoader: any;
declare var UnityProgress: any;

@Component({
  selector: 'app-unity-prefab-preview',
  templateUrl: './unity-prefab-preview.component.html',
  styleUrls: ['./unity-prefab-preview.component.css']
})
export class UnityPrefabPreviewComponent implements AfterViewInit {
  constructor() {}

  gameInstance: any;

  ngAfterViewInit() {
    this.gameInstance = UnityLoader.instantiate("gameContainer", "./webgl-app/Build/builds.json", { onProgress: UnityProgress });
  }

}

And then for the .html template I left this:

<div class="webgl-content">
  <div id="gameContainer" style="width: 960px; height: 600px"></div>
</div>

However, whatever approach I try (like using 'require' on the JS-files instead), the line in the ngAfterViewInit always gives an error: "Reference error: UnityLoader is not defined".

How should this be done properly so it would work?

  • The './webgl-app/Build/UnityLoader.js' is imported, yet shouldn't it be declared as a variable? Such as `import { UnityLoader } from './webgl-app/Build/UnityLoader.js';` – jervtub May 11 '17 at 15:29
  • Thanks for the suggestion! I tested this but I get the error on both that UnityLoader/UnityProgress is not a module. – Juhana Pietari Lehtiniemi May 11 '17 at 15:36
  • Does the _TemplateData/UnityProgress.js_ have any `export` declared functions? Only `export`-ed functions/variables can be imported afaik. – jervtub May 11 '17 at 15:44
  • Probably not, it comes straight from Unity and I'll try to keep the workflow so that I can just bring in the project as is. The original technique of declaring the javascript variables was something that was recommended for bringing in Javascript into Angular project in multiple articles, but I can't figure out why it doesn't work either. Writing script-tags into the template didn't work either, I guess they are stripped. I'm a bit out of ideas on what to try next... – Juhana Pietari Lehtiniemi May 11 '17 at 15:51
  • 2
    I managed to get the import work by adding export into the corresponding .json-files. After moving some of the webgl-app into the public assets-folder, I got it to almost run, but then the page says in a modal dialog: _An error occured running the Unity content on this page. See your browser JavaScript console for more info. The error was: ReferenceError: UnityLoader is not defined_ It seems this dialog is launched by Unity and not Angular, so I guess it almost runs, since there's a long load time. But still no luck, it needs the UnityLoader declared somewhere. – Juhana Pietari Lehtiniemi May 11 '17 at 16:23
  • 1
    @JuhanaPietariLehtiniemi You should post your solve as an answer so that this question can be marked complete. – Zze Dec 17 '17 at 01:04
  • I never got it to work properly at the end and gave up. – Juhana Pietari Lehtiniemi Dec 18 '17 at 11:01

4 Answers4

5

To add Unity WebGL to your project:

1) Export WebGL project to a /unity folder in your Angular project root (same level as src). Copy the following files from your Build folder to /src/assets/unity:

  • unity.json
  • unity.data.unityweb
  • unity.wasm.code.unityweb
  • unity.wasm.framework.unityweb

unity build files

2) Add UnityProgress.js and UnityLoader.js to your angular.json scripts:

"scripts": [
  "unity/TemplateData/UnityProgress.js",
  "unity/Build/UnityLoader.js"
],

3) (optional) Add style.css to your angular.json styles

"styles": [
  "node_modules/font-awesome/css/font-awesome.css",
  "src/assets/theme.scss",
  "src/styles.scss",
  "unity/TemplateData/style.css"
],

4) Add the base Unity HTML to your component.html:

<div class="webgl-content">
  <div id="unityContainer" style="width: 960px; height: 600px"></div>
  <div class="footer">
    <div class="webgl-logo"></div>
    <div class="fullscreen" (click)="unityInstance.SetFullscreen(1)"></div>
    <div class="title">UnityProject</div>
  </div>
</div>

5) Lastly, instantiate your unityContainer in your component.ts:

import { Component, OnInit, AfterViewInit } from '@angular/core';
declare var UnityLoader: any;

@Component({
  selector: 'app-example',
  templateUrl: './example.component.html',
  styleUrls: ['./example.component.css']
})
export class ExampleComponent implements OnInit, AfterViewInit {
  unityInstance: any;

  constructor() { }

  ngOnInit() { }

  ngAfterViewInit() {
    this.unityInstance = UnityLoader.instantiate("unityContainer", "assets/unity/unity.json");
  }

}
Z. Bagley
  • 8,942
  • 1
  • 40
  • 52
3

so I was messing around with this quite a time...

I have it up and running using the angular/cli@latest 1.6.5

here my app.component.ts

import { Component, NgZone, OnInit } from '@angular/core';
import * as $ from 'jquery';
declare const UnityLoader;
@Component({
 selector: 'app-root',
 templateUrl: './app.component.html',
 styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
public gameObject: any;

constructor(private ngZone: NgZone) {
  window.unity = window.unity || {};
  window.unity.GetUnityNumber = this.randomNumberFromUnity.bind(this);
}

public ngOnInit(): void {
 this.init();
}
public helloUnity() {
 console.log(this.gameObject); // <-- always undefined ?
 this.gameObject.SendMessage('SetText', 'HELLO?');
 }

private init() {
 $.getScript('assets/UnityLoader.js').done(function ( bla , text) {
   this.gameObject = 
   UnityLoader.instantiate('gameContainer','assets/test.json');
   //gameObject not undefined at this stage..
  });
}
private randomNumberFromUnity(input: string) {
  this.ngZone.run(() => {
      console.log('call from unity', input);
 });
}

}

In the typings.d.ts i have extended the window element.

interface Window {
  unity: any;
}

So when I do a call from unity I call it like

Application.ExternalCall("unity.GetUnityNumber", rnd);

All I'm missing right now is the call into unity... maybe you have an idea on this one?

I have the unity-build as a private npm package. When building everything is copied into the assets folder.

jonny
  • 343
  • 1
  • 3
  • 12
1

I simplified the Unity WebGL output to

unity-game.html

<body style="margin: 0px">
    <script src="TemplateData/UnityProgress.js"></script>
    <script src="Build/UnityLoader.js"></script>
    <script>
      var gameInstance = UnityLoader.instantiate("gameContainer", "Build/Browser.json", {onProgress: UnityProgress});
    </script>
    <div class="webgl-content">
      <div id="gameContainer" style="width: 800px; height: 600px;"></div>
    </div>
 </body>

and loaded it into an iframe. This gives you an

unsafe value used in a resource URL context

error. This can be addressed by using a special iframe Angular component.

unity-game.component.ts

import { Component, OnInit, Input } from '@angular/core';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';

@Component({
  selector: 'app-unity-game',
  templateUrl: './unity-game.component.html',
  styleUrls: ['./unity-game.component.css']
})
export class UnityGameComponent implements OnInit {
  @Input()
  url: string;
  urlSafe: SafeResourceUrl;

  constructor(public sanitizer: DomSanitizer) { }

  ngOnInit() {
    this.urlSafe= this.sanitizer.bypassSecurityTrustResourceUrl(this.url);
  }

}

unity-game.component.html

<iframe width="800" height="600" frameBorder="0" [src]="urlSafe"></iframe>

credit to Lrodriguez84: https://stackoverflow.com/a/50863439/719528

Then just add that special iframe component, passing it that HTML file wherever you would like the Unity project to appear!

<app-unity-game url="assets/unity-game.html"></app-unity-game>
Jehanlos
  • 77
  • 1
  • 8
1

I've got stuck on this aswell.

it seems to be your context is not set

$.getScript('assets/UnityLoader.js').done(function ( bla , text) {
   this.gameObject = 
   UnityLoader.instantiate('gameContainer','assets/test.json');
   //gameObject not undefined at this stage..
   // this does not point towards your container
});

solution :

$.getScript('assets/UnityLoader.js').done(function ( bla , text) {
    this.gameObject = 
    UnityLoader.instantiate('gameContainer','assets/test.json');
    //gameObject not undefined at this stage and this points to container
}.bind(this));

more info : https://docs.unity3d.com/2018.3/Documentation/Manual/webgl-interactingwithbrowserscripting.html

Nydrix
  • 11
  • 1