背景
工作中用到unity来制作实物展示等功能,
其中unity可以导出一些使用了WebGL的html和js文件,
而这个需要和目前的angular项目结合起来.
但由于unity官方的模板太过非专业,代码不利于移植,
只能选择将其特性在angular中重现.
前提知识
Webgl导出结构
1 2 3 4 5 6 7 8 9 10 11 12 13
| ├── Build │ ├── output.data.unityweb(项目资源等) │ ├── output.json(项目入口文件) │ ├── output.wasm.code.unityweb │ ├── output.wasm.framework.unityweb │ └── UnityLoader.js ├── index.html(用于web服务器的首页,可被替代) └── TemplateData(项目中的一些图片等资源) ├── favicon.ico ├── ... ├── style.css(加载前后使用的css) ├── UnityProgress.js(进度条显示工具) └── webgl-logo.png
|
其中 Build
文件夹下所有文件必不可少, TemplateData
文件夹下资源可以共用
index.html
是主要的被替代的文件.
unity官方太过非专业,导出的内容都一再变化.这里是2019版导出的内容
2020版的内容中,众多文件名发生了变化,同时也去掉了 output.json
和 UnityProgress.js
文件
整合到angular后结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| src ├── app │ ├── xxx-routing.module.tx │ ├── components │ │ ├── unity │ │ │ ├── index.ts │ │ │ ├── unity.component.css │ │ │ ├── unity.component.html │ │ │ ├── unity.component.spec.ts │ │ │ └── unity.component.ts . . . ├── assets │ ├── Build │ └── TemplateData . . .
|
可以将 Build
与 TemplateDate
放在 src/assets
下.
然后以组件的方式添加入口.
整合方针
- 将集中在一个html文件中的代码分离到angular的各个文件中
- 使用angular的风格,重写进度条的数值更新与显示与否的逻辑.
2019版本整合前后
整合前
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <!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 | Heart45</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 unityInstance = UnityLoader.instantiate("unityContainer", "Build/output.json", {onProgress: UnityProgress}); </script> </head> <body> <div class="webgl-content"> <div id="unityContainer" style="width: 960px; height: 600px"></div> <div class="footer"> <div class="webgl-logo"></div> <div class="fullscreen" onclick="unityInstance.SetFullscreen(1)"></div> <div class="title">Heart45</div> </div> </div> </body> </html>
|
外部文件说明:
style.css
主要用于管理背景图等等
UnityProgress.js
主要用于动态修改进度条
整合后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <ng-container> <div class="webgl-content"> <div #unityEl id="unityContainer" style="width: 960px; height: 600px"></div>
<div *ngIf="!isReady"> <div class="logo Dark"></div> <div class="progress Dark" style="width: 80%;"> <p style="color: white;">loading......</p> <div class="full" [style.width.%]="progress*90"></div> <div class="empty" [style.width.%]="(1-progress)*90"></div> <div class="number" style="color: white;">{{(progress * 100).toFixed(1)}}%</div> </div> </div>
<div class="footer"> <div class="webgl-logo"></div> <div class="fullscreen" (click)="toggleFullScreen()"></div> <div class="title">截面模型</div> </div> </div> </ng-container>
|
组件类要管理好各个状态的更新,比如progress,isReady等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| import { Component, OnInit, Input, OnDestroy } from '@angular/core';
@Component({ selector: 'unity', templateUrl: './unity.component.html', styleUrls: ['./unity.component.css'] }) export class UnityComponent implements OnInit, OnDestroy {
gameInstance: any; progress = 0; isReady = false;
constructor() { }
ngOnInit(): void { const loader = (window as any).UnityLoader;
this.gameInstance = loader.instantiate('gameContainer', `/assets/Build/output.json`, { onProgress: (gameInstance: any, progress: number) => { this.progress = progress; if (progress === 1) { this.isReady = true; } } }); }
ngOnDestroy(): void { this.gameInstance.Quit(); }
startStopRotating() { this.gameInstance.SendMessage('Director', 'StartStopRotating'); }
startStopAnimation() { this.gameInstance.SendMessage('Director', 'StartStopAnimation'); }
setDistance(distance: number) { this.gameInstance.SendMessage('Director', 'SetDistance', distance); }
toggleFullScreen() { this.gameInstance.SetFullscreen(1); }
}
|
配置Angular引用外部js库:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| { "projects": { "project-name": { "architect": { "build": { "options": { "scripts": [ "src/assets/Build/UnityLoader.js" ] } } } } } }
|
css可以引用assets中的,因此不用再考虑背景图片的问题
unity.component.css
1
| @import "../../../../assets/TemplateData/style";
|
路由方面,找到一个xx-routing.module.ts文件,关联api路径和使用的component类即可
1 2 3 4
| { path: 'unity1', component: UnityComponent, },
|
2020版本整合前后
整合前
相比与2019版本,有了一些改进
- 将进度条显示的js集成到了模板文件中
- 使用了一些promise的写法,不再是单纯的函数调用
- 增加了一些浏览器检测的逻辑
- 更合理的html和css结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| <!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 | DoctorNew</title> <link rel="shortcut icon" href="TemplateData/favicon.ico"> <link rel="stylesheet" href="TemplateData/style.css"> </head> <body> <div id="unity-container" class="unity-desktop"> <canvas id="unity-canvas"></canvas> <div id="unity-loading-bar"> <div id="unity-logo"></div> <div id="unity-progress-bar-empty"> <div id="unity-progress-bar-full"></div> </div> <div id="unity-progress-value">0.0%</div> </div> <div id="unity-mobile-warning"> WebGL builds are not supported on mobile devices. </div> <div id="unity-footer"> <div id="unity-webgl-logo"></div> <div id="unity-fullscreen-button"></div> <div id="unity-build-title">DoctorNew</div> </div> </div>
<script> const buildUrl = "Build"; const loaderUrl = buildUrl + "/output.loader.js"; const config = { dataUrl: buildUrl + "/output.data", frameworkUrl: buildUrl + "/output.framework.js", codeUrl: buildUrl + "/output.wasm", streamingAssetsUrl: "StreamingAssets", companyName: "DefaultCompany", productName: "DoctorNew", productVersion: "0.1", };
const container = document.querySelector("#unity-container"); const canvas = document.querySelector("#unity-canvas"); const loadingBar = document.querySelector("#unity-loading-bar"); const progressBarFull = document.querySelector("#unity-progress-bar-full"); const progressValue = document.querySelector("#unity-progress-value"); const fullscreenButton = document.querySelector("#unity-fullscreen-button"); const mobileWarning = document.querySelector("#unity-mobile-warning");
if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) { container.className = "unity-mobile"; config.devicePixelRatio = 1; mobileWarning.style.display = "block"; setTimeout(() => { mobileWarning.style.display = "none"; }, 5000); } else { canvas.style.width = "99vw"; canvas.style.height = "99vh"; } loadingBar.style.display = "block";
const script = document.createElement("script"); script.src = loaderUrl; script.onload = () => { createUnityInstance(canvas, config, (progress) => { progressBarFull.style.width = 100 * progress + "%"; progressValue.textContent = (100 * progress).toFixed(1) + "%"; }).then((unityInstance) => { loadingBar.style.display = "none"; fullscreenButton.onclick = () => { unityInstance.SetFullscreen(1); }; }).catch((message) => { alert(message); }); }; document.body.appendChild(script); </script> </body> </html>
|
整合后
首先引用外部库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| { "projects": { "project-name": { "architect": { "build": { "options": { "scripts": [ "src/assets/Build/xxx.loader.js" ] } } } } } }
|
然后思考重写一些逻辑时的关键变量
- 进度数值(用于控制进度条宽度)
- 是否已经加载成功
- 是否移动端
将这些反映在模板和组件类中即可
1 2 3 4 5 6 7 8 9 10 11 12 13
| <ng-container> <div id="unity-container"> <canvas id="unity-canvas" #unityCanvas></canvas>
<div id="unity-loading-bar" *ngIf="!isReady"> <div id="unity-logo"></div> <div id="unity-progress-bar-empty"> <div id="unity-progress-bar-full" [style.width.%]="progress*100"></div> </div> <div id="unity-progress-value">{{(progress * 100).toFixed(1)}}%</div> </div> </div> </ng-container>
|
组件类如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| import { Component, AfterViewInit, OnDestroy, ViewChild, ElementRef } from '@angular/core'; declare const createUnityInstance: any;
@Component({ selector: 'unity', templateUrl: './unity.component.html', styleUrls: ['./unity.component.css'] }) export class UnityComponent implements AfterViewInit, OnDestroy {
@ViewChild('unityCanvas',{static: false}) canvas: ElementRef;
gameInstance: any; progress = 0; isReady = false; isMobile = false;
constructor() { }
ngAfterViewInit(){
const buildUrl = "/assets/Build"; const config = { dataUrl: buildUrl + "/output.data", frameworkUrl: buildUrl + "/output.framework.js", codeUrl: buildUrl + "/output.wasm", streamingAssetsUrl: "StreamingAssets", companyName: "DefaultCompany", productName: "xxx", productVersion: "0.1", };
this.canvas.nativeElement.style.width = "960px"; this.canvas.nativeElement.style.height = "600px";
createUnityInstance(this.canvas.nativeElement, config, (progress) => { this.progress = progress; }).then((unityInstance) => { this.isReady = true; this.gameInstance = unityInstance; }).catch((message) => { alert(message); }); }
ngOnDestroy(): void { this.gameInstance.Quit(); }
startStopRotating() { this.gameInstance.SendMessage('Director', 'StartStopRotating'); }
startStopAnimation() { this.gameInstance.SendMessage('Director', 'StartStopAnimation'); }
setDistance(distance: number) { this.gameInstance.SendMessage('Director', 'SetDistance', distance); }
toggleFullScreen() { this.gameInstance.SetFullscreen(1); }
}
|
附带一个能用的css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| body { padding: 0; margin: 0 } #unity-container { position: absolute } #unity-container.unity-desktop { left: 50%; top: 50%; transform: translate(-50%, -50%) } #unity-container.unity-mobile { width: 100%; height: 100% } #unity-canvas { background: #231F20 } .unity-mobile #unity-canvas { width: 100%; height: 100% } #unity-loading-bar { position: absolute; left: 50%; top: 50%; width:450px; transform: translate(-50%, -50%) } #unity-logo { height: 130px; background: url('unity-logo-dark.png') no-repeat center } #unity-progress-bar-empty { width: 88%; height: 18px; margin-top: 10px; float:left; background: url('progress-bar-empty-dark.png') no-repeat; background-size: 100% 18px } #unity-progress-bar-full { width: 0%; height: 18px; background: url('progress-bar-full-dark.png')} #unity-progress-value {width: 12%; height: 18px; font-size: 15px; margin-top: 10px; float: right; color: white; text-align: right} #unity-footer { position: relative } .unity-mobile #unity-footer { display: none } #unity-webgl-logo { float:left; width: 204px; height: 38px; background: url('webgl-logo.png') no-repeat center } #unity-build-title { float: right; margin-right: 10px; line-height: 38px; font-family: arial; font-size: 18px } #unity-fullscreen-button { float: right; width: 38px; height: 38px; background: url('fullscreen-button.png') no-repeat center } #unity-mobile-warning { position: absolute; left: 50%; top: 5%; transform: translate(-50%); background: white; padding: 10px; display: none }
|
参考
- 看起来是一个主要写C#和asp.net的大叔的博客
- 他的代码地址