U3DC.COM | 优三帝研究院

Menu

Unity WebGL 开发指北(完全篇)

前言

随着轻量化3D需求的增长,WebGL方案越来越受到关注,也有越来越多的应用场景诸如三维可视化,数字孪生、元宇宙区块链Web游戏等。相比传统的客户端应用,WebGL采用BS架构,即浏览器就可以直接体验3D效果。举个不是很恰当的例子:假如你是做客户端应用的,你和另一个做WebGL的竞争对手同时去竞标一个项目,你演示项目的时候,还要让甲方掏出手机,打开应用商城,搜索app,下载,安装,然后再打开,最后演示。而你的竞争对手,直接转发了一个链接过去,立即就开始演示了,这便捷程度,差不是一点半点了。

局限性

当然,也不是说WebGL就没缺点了(丑话说前头 >_< )。主流的WebGL开发方案包括:原生方案、渲染库(如ThreeJs)、基于跨平台的引擎发布(如本文重点要说的Unity),他们都各有优缺点。使用Unity的优势是开发效率高,一次开发,可多平台部署,当然缺点也明显,引擎内核重,不优化的话,执行效率上并没有优势,当然这也是本文要探讨的重点。

Unity 中的 WebGL 平台旨在替代以前的 UnityPlayer 平台,随着目前多数浏览器都已经能够很好的支持 Html5,因此 WebGL 也更加成熟,为了将项目发布为 WebGL 平台,在打包的时候,Unity 利用 Emscripten 工具链,将引擎的 C/C++ 代码转换为 WebAssembly(一种浏览器可以执行的格式,更加高效),而 C#代码则需要先通过 IL2CPP转换为 C/C++代码,在转换为 WebAssembly。

虽然目前 Unity WebGL 程序能够在大部分主流浏览器上运行,但是也存在一定差异,另外,在手机浏览器上是无法正常运行 WebGL 程序的(2022.3更新:当然不绝对,修改模板适配,其实是能跑的)。

手机浏览器运行Unity WebGL方法:将UnityLoader.js中的UnityLoader.SystemInfo.mobile["Edge", "Firefox", "Chrome", "Safari"].indexOf(UnityLoader.SystemInfo.browser) == -1替换成false

另外由于平台限制,有些功能在 WebGL 上是不支持的:

浏览器支持

Unity WebGL 虽然在大部分浏览器上都支持,但是支持程度以及性能表现不一样,另外,在移动设备上是不支持的,虽然在一些高端设备上有可能运行,但是绝大部分设备是没有那么大内存来支持 Unity WebGL 的。

下表列出了FirefoxChromeSafriEdge各个浏览器的支持情况

说明:

  1. 浏览器厂商为了稳定性,通常会采用黑名单/白名单的方式,针对不同的显卡驱动进行功能限制,在 chrome 中可以通过输入 chrome://gpu 来查看当前 gpu 的状态,或者可以在这里查询;
  2. Large-Allocation 响应头是高速浏览器需要分配大量内存,目前只有 Firefox 支持。

编译运行 WebGl 项目

WebGL 项目打包之后,会生成如下文件结构:

Build 文件夹下载 *.unityweb 文件,可能是压缩文件,由 PlayerSetting/publishing setting/Compression Format 设置。

生成完毕之后,可以用浏览器打开 index.html 文件,由于 Chrome 不允许运行本地文件,因此需要将生成文件放到 Web 服务器,或者通过 unity 的 Build And Run,unity 会在编译完成之后启动一个本机 web 服务器。

调试模式

可以获得什么:

缺点:

WebGL 平台不要在 VS 中进行调试,而要使用 Chrome 的开发者工具(F12)进行调试。

常见错误:

Problem: The build runs out of memory
这通常在 32 位浏览器上出现,原因是内容占用内存过大,浏览器分配内存失败。

Problem: Files saved to Application.persistentDataPath do not persist
持久化文件没有保存完成,Unity WebGL 将所有持久化数据(PlayerPrefs、缓存)都保存到浏览器的 IndexedDB 中,在开发者工具中,可以通过 Application 标签页查看,这一API 是异步的,所以不知道什么时候能够完成。

Error message: Incorrect header check
通常是服务器配置不正确。

Error message: Decompressing this format (1) is not supported on this platform
通常是加载 LZMA 压缩格式的 AssetBundle 时发生,WebGL 平台不支持 LZMA 格式,改用 LZ4 格式压缩。

重要发布设置

Disable HW Statistics

默认不勾选,表示将会在加载内容时,向 Unity 发送你的硬件信息、用户信息等,建议勾选上,因为国内网络访问 Unity 服务器比较慢,会降低内容的加载速度。

如果勾选的话,在 Chrome 中可以看到它会向 https://config.uca.cloud.unity3d.com/ 发送请求,可以看到他的发送内容。

代码裁剪

Strip Engine Code

默认勾选,表示对于项目中没有用到类型,将会把那一部分代码剔除掉,从而减少编译大小,提高运行时的性能。Unity 会扫描项目中所有从 UnityObject 继承的类型,包括检查它的内部引用以及序列化字段,将会移除没有任何引用的类型,从而减少发布大小,生成的代码也更少、更快、内存占用更少。

代码裁剪可能引起的问题:

可能会裁剪掉实际用到的类型,比如预制体包含一个类型 A,而将该预制体打包到 AssetBundle 之中,Unity 就可能将 A 类型裁剪掉,运行时将在控制到看到错误 Could not produce class with ID XXX,可以按照下面两步来解决该问题:

  1. 根据提示 ID,查找裁剪的类型,查找地址:这里
  2. 将该类型在脚本中引用一下,或者在创建 Assets 目录下 link.xml,格式如下。
<linker>
    <assembly fullname="UnityEngine">
        <type fullname="UnityEngine.Collider" preserve="all"/>
    </assembly>
</liner>

如果想要查看裁剪之后 Unity 包含了内些类型,可以在打包之后,找到文件 Temp/StagingArea/Data/il2cppOutput/UnityClassRegistration.cpp 进行查看,除此之外没有其他更加方便的方法。

另一一点需要知道的是 Strip Engine Code选项只是针对 Unity 的本机代码,对于托管代码来说,总是会进行代码裁剪,IL2CPP 通过托管 DLL,以及代码脚本中的静态引用,来进行代码裁剪,如果代码中使用反射来获取类型,那么同样的,该类型有可能被裁剪掉,因此也需要添加到 linker.xml中。

Enable Exceptions

该设置主要用来设置如何处理程序中的异常:

Data Caching

默认启用,会使用资源缓存到浏览器的 IndexedDB 数据库,在后面运行的时候不用再次从服务器下载资源,不同浏览器的缓存策略也不尽相同。

如果只是想进行空引用检查和数组越界检查,而不想完全支持异常,可以通过如下代码来支持:

using UnityEditor;

public class WebGLEditorScript
{
    [MenuItem("WebGL/EnableNullChecks")]
    public static void EnableNullChecks()
    {
        PlayerSettings.SetPropertyString("additionalIl2CppArgs", "--emit-null-checks --enable-array-bounds-check",UnityEditor.BuildTargetGroup.WebGL);
    }
}

降低发布尺寸

WebGL 平台的内容是需要用户通过浏览器,从 Web 服务器上进行下载,所以控制好发布大小,减少用户的加载时间是非常有必要的,可以通过以下几种方式帮助实现:

AssetBundles

因为 WebGL 在启动之前,需要将所有的资源预加载完成,因此减少启动时间的一个有效方式就是减少系统资源,或者说减少发布资源,可以利用 AssetBundle 将资源从主包中分离出来,这样,只需要加载一个非常小的加载场景即可,另外 AssetBundle 还能帮助资源管理。

在 WebGL 平台使用 AssetBundle 有以下几点需要注意:

改变 Build 文件夹位置

当把发布完成的内容放到服务器时,可能需要将 Build 文件夹放到其他的位置,这里需要修改两个地方:

  1. index.html 中修改 json 文件的 url;
  2. 修改 json 文件中各个 url,其中的相对位置,会认为是相对 json 文件的位置。

增量编译

IL2CPP 采用增量式编译,代码的编译结果会缓存到 Library/il2cpp_cache,如果代码没有变化,将不会进行再次编译,如果需要进行重新编译,只需要删除该文件夹即可。

压缩打包

前面提过在 PlayerSetting 中可以设置发布内容的压缩格式,分别是:

目前主流的浏览器都已经支持了压缩格式,能够在 http(s)传输过程中,将压缩的数据进行解压,这在效率上要比 js 进行解压要快很多。

另外,unity 在发布时也提供了 js 实现的解压器,如果浏览器解压失败的话,将会采用 js 解压的方式,当然这会带来一些性能损失,增长内容加载时间。

为了让浏览器在 http(s)传输过程中进行解压,需要Web 服务器通过 http(s)头来告诉浏览器,传输内容的压缩格式,就需要对 Web 服务器进行相应的设置。

Apache 服务器设置

设置 Build 文件夹中的 .htaccess 文件,如果没有进行创建:

gzip 压缩格式设置

<IfModule mod_mime.c>
  AddEncoding gzip .unityweb
</IfModule>

grotli 压缩设置

<IfModule mod_mime.c>
  AddEncoding gzip .unityweb
</IfModule>

IIS 服务器设置

IIS 默认是无法访问未知的 MIME 类型,所以需要先给 .unityweb 指定 MIME 类型,这里有两种方式:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
            <staticContent>
            <remove fileExtension=".unityweb" />
<mimeMap fileExtension=".unityweb" mimeType="application/octet-stream" />
            </staticContent>
    </system.webServer>
</configuration>

上面设置完成之后,需要设置压缩格式支持,同样在 Build目录创建 web.config文件,内容如下:

gzip 压缩

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
            <staticContent>
                    <remove fileExtension=".unityweb" />
                    <mimeMap fileExtension=".unityweb" mimeType="application/octet-stream" />
            </staticContent>
            <rewrite>
                    <outboundRules>
                        <rule name="Append gzip Content-Encoding header">
                            <match serverVariable="RESPONSE_Content-Encoding" pattern=".*" />
                            <conditions>
                                    <add input="{REQUEST_FILENAME}" pattern="\.unityweb$" />
                            </conditions>
                            <action type="Rewrite" value="gzip" />
                        </rule>
                    </outboundRules>
            </rewrite>
    </system.webServer>
</configuration>

brotli 压缩

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
            <staticContent>
                    <remove fileExtension=".unityweb" />
                    <mimeMap fileExtension=".unityweb" mimeType="application/octet-stream" />
            </staticContent>
            <rewrite>
                    <outboundRules>
                        <rule name="Append br Content-Encoding header">
                            <match serverVariable="RESPONSE_Content-Encoding" pattern=".*" />
                            <conditions>
                                    <add input="{REQUEST_FILENAME}" pattern="\.unityweb$" />
                            </conditions>
                            <action type="Rewrite" value="br" />
                        </rule>
                    </outboundRules>
            </rewrite>
    </system.webServer>
</configuration>

流式 WebAssembly

从 2019.2开始,增加了 WebAssembly streaming 选项,它能够提高内容的启动速度,它是通过浏览器在下载 WebAssembly 的同时,进行 WebAssembly 的转换和编译。

Web 服务器需要进行相关配置,和配置压缩格式类似。

Apache 服务器配置

修改 .htaccess 文件

如果未压缩:

<IfModule mod_mime.c>
  AddType application/wasm .wasm
</IfModule>

gzip 压缩

<IfModule mod_mime.c>
  AddEncoding gzip .unityweb
  AddEncoding gzip .wasm
  AddType application/wasm .wasm
</IfModule>

brotli 压缩

<IfModule mod_mime.c>
  AddEncoding br .unityweb
  AddEncoding br .wasm
  AddType application/wasm .wasm
</IfModule>

IIS 设置

修改 Build 文件夹的 web.config 文件。

未压缩设置

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <staticContent>
            <remove fileExtension=".unityweb" />
            <mimeMap fileExtension=".unityweb" mimeType="application/octet-stream" />
            <remove fileExtension=".wasm" />
            <mimeMap fileExtension=".wasm" mimeType="application/wasm" />
        </staticContent>
    </system.webServer>
</configuration>

gzip 设置

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <staticContent>
            <remove fileExtension=".unityweb" />
            <mimeMap fileExtension=".unityweb" mimeType="application/octet-stream" />
            <remove fileExtension=".wasm" />
            <mimeMap fileExtension=".wasm" mimeType="application/wasm" />
        </staticContent>
        <rewrite>
            <outboundRules>
                <rule name="Append gzip Content-Encoding header">
                    <match serverVariable="RESPONSE_Content-Encoding" pattern=".*" />
                    <conditions>
                        <add input="{REQUEST_FILENAME}" pattern="\.(unityweb|wasm)$" />
                    </conditions>
                    <action type="Rewrite" value="gzip" />
                </rule>
            </outboundRules>
        </rewrite>
    </system.webServer>
</configuration>

brotli 设置

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <staticContent>
            <remove fileExtension=".unityweb" />
            <mimeMap fileExtension=".unityweb" mimeType="application/octet-stream" />
            <remove fileExtension=".wasm" />
            <mimeMap fileExtension=".wasm" mimeType="application/wasm" />
        </staticContent>
        <rewrite>
            <outboundRules>
                <rule name="Append br Content-Encoding header">
                    <match serverVariable="RESPONSE_Content-Encoding" pattern=".*" />
                    <conditions>
                        <add input="{REQUEST_FILENAME}" pattern="\.(unityweb|wasm)$" />
                    </conditions>
                    <action type="Rewrite" value="br" />
                </rule>
            </outboundRules>
        </rewrite>
    </system.webServer>
</configuration>

Part2. 在前文中,主要写了一些关于 WebGL 发布、调试、以及开发限制等问题,本章主要是关于 WebGL平台中的图形渲染、网络、音频相关的东西,WebGL 是在 OpenGL ES 发展来的,WebGL 1.0 基于 OpenGL ES 2.0,WebGL 2.0 基于 OpenGL ES 3.0,目前主流火狐和 Chrome 已经支持 WebGL 2.0,而 Safari 和 Edge 还不支持 2.0。

渲染相关

Camera Clear

WebGL 在每帧最后都会清除frame buffer,即使 Camera.clearFlags 设置了 DontClear 也不起作用,如果需要改变这种默认行为,可以通过修改发布模板中的 index.html 文件。

UnityLoader.instantiate("unityContainer", "%UNITY_WEBGL_BUILD_URL%", {
    Module: {
        "webglContextAttributes": {"preserveDrawingBuffer": true},
    }
});

延迟渲染

Unity WebGL 平台只在支持 WebGL 2.0 下支持延迟渲染,WebGL 1.0 将会采用前向渲染。

全局光照

只支持 baked GI,实时 GI 不支持,此外,只支持 Non-Directional光照贴图。

MovieTexture

WebGL 不支持通过 MovieTexture 播放视频,一种替代的方法是,使用 Html5 的视频组件。

字体渲染

WebGL 支持动态字体,但是由于它不能访问本机文件系统,所以使用到的字体文件必须放到项目当中。(包括 Fallback fonts,以及粗体和斜体字体),另外需要注意的是,默认的Arial字体在浏览器中不支持中文,需要自行替换字体以支持。

为了提高性能,建议在 WebGL 中使用 TMP 来替代默认的 UGUI 文本组件,使用静态字体来替代动态字体。

抗锯齿

绝大部分的浏览器和 GPU 都支持抗锯齿,只需要在 Quality 设置中启用抗锯齿即可,但是在 WebGL 1.0 中存在一些限制:

WebGL2.0 不存在以上限制。

网络相关

处于安全考虑,JavaScript 没有直接访问 IP 套接字的权限,所以 System.Net 命名空间下的全部类型,System.Net.Sockets 命名空间下的部分类型,在 WebGL 平台是不支持的,同样的还有 UnityEngine.Network* 前缀的类型。

如果需要访问网络,有以下几种选择:

WWW

在 WebGL 平台,WWWUnityWebRequest都是用 JavaScript 的XMLHttpRequest来实现,通过浏览器来处理 WWW 请求。

假设我们把 WebGL 内容部署在服务器 A,而在运行时,我们需要用 WWW 从另一个服务器 B 下载资源,这就需要 B 服务器通过 CORS 进行授权,如果服务器 B 没有进行 CORS 设置,那么将会在控制台看到如下错误:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://myserver.com/. This can be fixed by moving the resource to the same domain or enabling CORS.

更多 CORS 信息可以参考这里

不要使用阻塞代码

不要是阻塞代码来等待 WWW 或 WebRequest 完成,比如下面代码:

while(!www.isDone){}

因为 WebGL 是单线程,而 XMLHttpRequest 又是异步方法,整个一直执行 while 循环,而不能更新 XMLHttpRequest 响应信息,可以使用协程和 yield 来等待下载完成。

WebSocket

在新版 Unity 网络模块中,可以设置 Networking.NetworkManager.useWebSockets 来启用 WebSocket 协议。

目前浏览器基本 都支持 WebSocket 和 WebRTC(Safari 不支持),但是Unity 没有公开这两个 API,如果要使用的话,可以使用 JavaScript 插件。

音频相关

与其他平台采用 FMOD 音频引擎不同, WebGL 由于不支持多线程,因此采用基于 WebApi 的音频控制模块,让浏览器来控制音频的混合和播放,因此 WebGL 音频使用存在很多限制,下面列举 WebGL 平台支持的音频功能,如果没有列出,则说明 WebGL 平台不支持该功能。

AudioSource

支持属性:

支持函数:

AudioListener

所有的 API 都支持

AudioClip

WebGL 总是将音频剪辑压缩格式设置为 ACC,因为该格式在浏览器支持比较广泛。

支持的属性:

支持的方法:

SystemInfo.supportsAudio

WebGL 平台没有实现该参数,总是返回 true。

WWW.audioClip

只有浏览器原生支持的音频格式,才能够通过通过 WWW.audioClip 进行加载,详细的支持格式可以查询这里

如果只是选择音频格式,可以看下面的表格。

视频格式选择

Microphone

不支持


Part3.最后我们再来探讨下性能问题、内存、与浏览器的交互。


性能问题


WebGL 应用在 GPU 上的性能应该与原生应用接近,因为 WebGL 也会使用 gpu 进行硬件加速,并且从 WebGL API 转换到操作系统的图形 API 性能消耗非常小。
CPU 方面,WebGL 平台将代码转换为 WebAssembly,它的性能取决于浏览器,各个浏览器之间的对比可以参考这里
还有一些其他因素会影响性能,这里主要是 JavaScript 不支持多线程,Unity做的很多多线程优化在 WebGL 平台不起作用。
为了达到最优的性能,建议最终发布时,将 Exception support 设置为 none。
当设置了 runInBackground之后,即使画布或者浏览器失去焦点之后,仍然会在后台运行,然后有些浏览器会对后台运行的标签进行限制,如果 WebGL 内容所在标签不可见之后,将会每秒更新一次,这将会造成 Time.deltaTime 比实际要慢,这是因为 Unity 有个 Time.maximumDeltaTime 设置,这个值默认为 0.1,Time.deltaTime 不会小于此值,就是说实际已经过了 1秒,但是系统只步进了 0.1 秒。
如果想以低帧率运行,可以通过 Application.targetFrameRate 进行设置,如果不需要限制,将该参数设置为 -1,而非一个非常高的值。


内嵌资源


有些 .net 程序集中会内嵌一些资源,默认情况下,WebGL 在打包时是不打包这些资源的,目的是为了减少内容的大小,如果有些 API 确实需要这些资源,可以通过以下代码进行设置:

using UnityEditor;

public class WebGLEditorScript
{
    [MenuItem("WebGL/Enable Embedded Resources")]
    public static void EnableErrorMessageTesting()
    {
        PlayerSettings.SetPropertyBool("useEmbeddedResources", true, BuildTargetGroup.WebGL);
    }
}


当启用设置之后,项目中 .net 程序集的内嵌资源都会打包到 WebGL 发布内容中。


WebGL 内存


内存是制约 WebGL 内容复杂度的一个重要因素,WebGL 内容运行在浏览器中,可用的内存大小取决于浏览器的类型和设备类型,主要由以下几点决定:

下图是 Unity WebGL 在浏览器运行时的内存情况

可以看出,除了 Unity Heap 之外,还需要浏览器分配额外的内存,理解这一点有助于帮助后续的内存优化。
DOM、Unity Heap、Asset Data、Code 这几块内存在加载之后将会常驻内存,其他的像 AssetBundle、WebAudio 等都是由代码进行控制加载和卸载。


Unity Heap


UnityHeap 包括几部分组成:

UnityHeap 的大小可以通过 PlayerSetting->Memory Size 来设置,默认为 256Mb,在 JavaScript 中,UnityHeap 是通过 TypedArray 来表示的,即一旦分配之后就不能增大或减小,并且这部分内存是不返还给浏览器的。
通常一个空的项目只需要 16Mb 即可运行,堆内存大小可以根据项目复杂度进行调整,但是需要记住,Unity Heap越大,所能使用的用户就越少。因此在实际项目中为了尽可能的减小 Unity Heap 的大小,我们通常需要将我们的项目整个跑一遍,然后通过 Profiler 来查看最终用了多少内存,然后调整一个稍大的值(16整数倍)即可满足需要。


资源数据(Asset Data)


在编译 WebGL 项目时,Unity 会创建一个a.data文件,它包含了项目的所有场景和资源,因为 WebGL 不能访问系统的文件系统,因此只能在开始之前进行下载,然后解压到浏览器分配的一块连续内存,并且会一直常驻在内存中,所以,如果想要提高加载速度,应该尽可能的降低资源的大小。
另一种有推荐的方法是采用 AssetBundle,它具备以下几点优势:

内存相关问题


在调试过程中如果遇到内存相关的问题,首先要区分是浏览器分配失败,还是 Unity 从预分配块中分配失败。如果是浏览器分配失败,这说明 WebGL 内容内存占用过大,应该降低资源大小;如果时 Unity 从块中分配内存失败,这说明在 PlayerSetting 中设置的内存太小,应该适当增加。
那么如何判断这两种情况?不同的浏览器可能显示的结果不同,但是一般的,如果在控制台看到类似 Out of memory的错误,通常是浏览器分配失败;如果在加载内容时发生崩溃,并且没有显示什么错误,造成这种问题的原因很多,但通常都是 Unity 内存分配失败。


Large-Allocation


服务器在相应资源请求是,可以在 Http 头中标记 Large-Allocation,它告诉浏览器(目前只有火狐支持)我要使用大量的内存,这可以解决 32 位中堆内存分配问题。


垃圾回收


在堆内存中分配的对象,当没有任何引用指向它们时,将会被当做垃圾进行回收,在 WebGL 平台也是如此。和其他平台不同的是,GC 调用的时机不同,在其他平台,为了执行GC,通常会暂停所有线程,然后检查各个线程栈进行垃圾回收,但是在 WebGL 这是行不通的,因为 WebGL 是单线程,因此只在已知栈为空时WebGL 才进行 GC 调用(目前是每帧结束调用一次)。
这通常不会有什么问题,因为每帧的产生的垃圾内存很少,但是下面一段代码时不可行的,因为 WebGL 是单线程,在循环过程中,根本没有机会执行 GC 操作,所以造成的结果就是垃圾越来越多,最后内存分配失败。

string hugeString = "";

for(int i=0;i<10000;i++)
{
    hugeString += "foo";
}



关于内存的更多信息,可以参考Understanding Memory in Unity WebGL,和Unity WebGL Memory: The Unity Heap


与浏览器交互


在构建 WebGL 项目中,可能会需要与 Html 中的元素进行交互,或者通过 WebAPI 实现某些功能,这些都需要和浏览器的 JavaScript 进行交互,下面介绍一下应该怎么做。


从 C# 调用 JavaScript 代码

涂启标:Unity(WebGL)与JS通讯2022最新姿势


为了调用 JavaScript 代码,首先需要将 JavaScript 文件后缀名修改为 .jslib,并且放到 Assets/Plugins/WebGL 目录,JavaScript 文件需要按照下面的格式。

mergeInto(LibraryManager.library, {

  Hello: function () {
    window.alert("Hello, world!");
  },

  HelloString: function (str) {
    window.alert(Pointer_stringify(str));
  },

  PrintFloatArray: function (array, size) {
    for(var i = 0; i < size; i++)
    console.log(HEAPF32[(array >> 2) + i]);
  },

  AddNumbers: function (x, y) {
    return x + y;
  },

  StringReturnValueFunction: function () {
    var returnStr = "bla";
    var bufferSize = lengthBytesUTF8(returnStr) + 1;
    var buffer = _malloc(bufferSize);
    stringToUTF8(returnStr, buffer, bufferSize);
    return buffer;
  },

  BindWebGLTexture: function (texture) {
    GLctx.bindTexture(GLctx.TEXTURE_2D, GL.textures[texture]);
  },

});



在 C# 端,按照下面格式进行导入。

using UnityEngine;
using System.Runtime.InteropServices;

public class NewBehaviourScript : MonoBehaviour {

    [DllImport("__Internal")]
    private static extern void Hello();

    [DllImport("__Internal")]
    private static extern void HelloString(string str);

    [DllImport("__Internal")]
    private static extern void PrintFloatArray(float[] array, int size);

    [DllImport("__Internal")]
    private static extern int AddNumbers(int x, int y);

    [DllImport("__Internal")]
    private static extern string StringReturnValueFunction();

    [DllImport("__Internal")]
    private static extern void BindWebGLTexture(int texture);

    void Start() {
        Hello();

        HelloString("This is a string.");

        float[] myArray = new float[10];
        PrintFloatArray(myArray, myArray.Length);

        int result = AddNumbers(5, 7);
        Debug.Log(result);

        Debug.Log(StringReturnValueFunction());

        var texture = new Texture2D(0, 0, TextureFormat.ARGB32, false);
        BindWebGLTexture(texture.GetNativeTextureID());
    }
}



在传递参数和返回值时,需要注意以下几点:


JavaScript 调用 C# 代码

涂启标:前端JS脚本调用Unity内的函数2022


从 JavaScript 调用 C#方法,需要获取 unityInstance 对象的 SendMessage 函数,具体格式如下:

unityInstance.SendMessage(objectName,methodName,value);

调用 C 方法


在其他平台如果想要调用 C 函数,需要将 C 代码编译为动态链接库,而在 WebGL 平台则是通过emscripten 工具将 C 的源码转换为 JavaScript 代码,所以需要将 C 源码文件,放到 Assets/Plugins/WebGL 目录中。C 代码的编写和其他平台类似,如果时 C++ 代码,需要导出 C 声明接口。

#include <stdio.h>

extern "C" void Hello ()
{
    printf("Hello, world!\n");
}

extern "C" int AddNumbers (int x, int y)
{
    return x + y;
}


WebGL 模板


Unity 在打包 WebGL时,会将 player 嵌套进一个 Html 文件中,Unity 有两个内置的 hmtl 模板可以选择,但是在正式生产中,我们可能需要根据自己的需要,进行定制,可以按照下面的方法进行模板定制。


首先必须将自定义模板放到 Assets/WebGLTemplates/模板名称 文件夹内,每个模板必须包含一个 index.html文件,依赖的资源也需要放置到该文件夹内,当模板创建完成之后,可以在PlayerSetting中选择创建的模板,可以通过 thumbnail.png(128*128 像素)文件给刚创建的模板,添加一个预览图标,方便进行选择。


index.html 文件至少需要包含三个元素:

UnityLoader.instantiate(container, url, override)


container,用于展示内容的容器,通常传入 div 的 id;
url,打包时 json 文件地址,通常指定一个宏:%UNITY_WEBGL_BUILD_URL%
override,可选项,用于重写某些预制行为。


模板中可以使用的宏


可以通过宏来获取打包内容的信息,宏的格式为 %宏名称%,在打包时会将这些宏进行展开,可用的宏列表如下:

添加进度条


Unity WebGL 会提供一个默认的加载进度条,当然可以进行自定义。通过重写 UnityLoader.instantiate 中的 override 的 onProgress 行为。

var unityInstance = UnityLoader.instantiate("unityContainer", "%UNITY_WEBGL_BUILD_URL%", {onProgress: UnityProgress});



UnityProgress 方法包含两个参数,第一个参数表示 unityInstance 对象,第二个参数表示加载进度。

function UnityProgress(unityInstance, progress) {
  if (!unityInstance.Module)
    return;
  if (!unityInstance.logo) {
    unityInstance.logo = document.createElement("div");
    unityInstance.logo.className = "logo " + unityInstance.Module.splashScreenStyle;
    unityInstance.container.appendChild(unityInstance.logo);
  }
  if (!unityInstance.progress) {
    unityInstance.progress = document.createElement("div");
    unityInstance.progress.className = "progress " + unityInstance.Module.splashScreenStyle;
    unityInstance.progress.empty = document.createElement("div");
    unityInstance.progress.empty.className = "empty";
    unityInstance.progress.appendChild(unityInstance.progress.empty);
    unityInstance.progress.full = document.createElement("div");
    unityInstance.progress.full.className = "full";
    unityInstance.progress.appendChild(unityInstance.progress.full);
    unityInstance.container.appendChild(unityInstance.progress);
  }
  unityInstance.progress.full.style.width = (100 * progress) + "%";
  unityInstance.progress.empty.style.width = (100 * (1 - progress)) + "%";
  if (progress == 1)
    unityInstance.logo.style.display = unityInstance.progress.style.display = "none";
}

【引用&参考】

Understanding Memory in Unity WebGL | Unity Blog
Unity WebGL Memory: The Unity Heap | Unity Blog
WebAssembly Load Times and Performance | Unity Blog
Unity(WebGL)与JS通讯2022最新姿势
前端JS脚本调用Unity内的函数2022
Unity开发优化方案:WebGL篇 | U3DC.COM

打赏
— 于 共写了19848个字
— 文内使用到的标签:

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据