Version: 1.7
语言 : 中文
平台支持
微信平台支持

小游戏平台简介

小游戏与传统原生 APP 存在一些细微但关键的区别,可能会对开发过程带来挑战。本节详细介绍小游戏的特点及平台适配建议,帮助开发者解决常见问题,优化游戏性能。

内存

小游戏由于运行机制和文件系统的特殊性,内存结构与原生 App 不同,通常比原生 App 消耗更多的内存。典型游戏的内存占用如下图所示:

内存使用 描述
基础库 + Canvas 在小游戏环境中并不存在 DOM,但依然会存在一些基本消耗,比如小游戏公共库,Canvas 画布等。典型地,小游戏公共库约占用内存 100~150MB,Canvas 画布与设备物理分辨率相关,比如 iPhone 11 Pro Max 占用约80MB。
Unity Heap 托管堆、本机堆与原生插件底层内存。举例,游戏逻辑分配的 C# 对象等托管内存、Unity 管理的 AssetBundle 和场景结构等本机内存、第三方原生插件(如lua)调用的 malloc 分配。
WASM 编译 代码编译与运行时指令优化产生的内存,在 Android v8、iOS JavascriptCore 中还需要大量内存进行JIT优化
GPU内存 纹理或模型 Upload GPU 之后的显存占用,若使用 Unity2021 之前不支持压缩纹理的版本,纹理内存会造成明显膨胀。
音频 将音频传递给容器(浏览器或小游戏)后,播放音频时将占用的内存。目前 UnityAudio 将自动适配小游戏,特别地请避免使用fmod播放长音频。
其他
  • Emscripten 使用文件系统模拟 Linux/POSIX 接口,代价是占用与文件同等大小的内存。 请勿使用首资源包、Addressable Cache 机制、WWW.LoadFromCacheOrDownload 等 Cache API*
  • 网络请求造成的浏览器端 JS 临时内存、垃圾回收

下图是同一款游戏以原生 App 和小游戏运行时的内存对比。

从上图来看,相比原生 App,小游戏内存占用多了 450MB 左右,增大之处主要在于:

  • WASM 的加载与编译多占了约 340M

  • WASM Heap 中的未分配使用部分多了约 90M

  • JS File System 占用多了约 60M

WASM 文件本身大小有 33.8MB,浏览器内核在代码编译执行时会产生更多的内存消耗,相关的缓存、JIT 优化也会使用较多内存,总体大约是 WASM 文件大小的10倍左右。这一大块内存消耗是原生 App 所没有的。

另外由于浏览器的沙盒机制,WebGL 上无法访问本地文件系统,Emscripten 使用 JS + IndexedDB 模拟了一个文件系统,WASM 访问 JS 层,JS 层再与 IndexedDB 定期同步,因此 JS 中始终存有所有文件的一份 copy。在小游戏中,应该尽量避免使用 Emscripten 文件系统。

相比原生 App,Native Heap 内存和显存占用也有显著减少,主要原因是纹理压缩格式,团结引擎也针对此提供具体优化方案。

此外,Mono Heap 由 Il2cpp 分配管理,其他 native 内存(包括引擎 Native Heap 和其他第三方库如 Lua)由 Emscripten 的 malloc 分配管理(默认使用 dlmalloc)。这两部分都只增不减,而且相互独立,空闲空间无法共享,因此需要各自都注意控制峰值。

iOS 设备上,小游戏的内存十分受限,低档机不能使用超过 1G 内存,高档机的内存上限也在 1.4G 左右。一旦超过这个限制,就很可能被操作系统触发 OOM,重启。

因此针对小游戏的内存优化非常重要。

CPU

CPU 性能从公开的 Benchmark 数据,以及我们对比测试的结果来看,WASM 执行效率为 Native 的三分之一。此外 WASM 是单线程,并且不支持 SIMD,因此小游戏的 CPU 性能要比原生低不少。

带来的结果是,原本在 App 端不明显的性能瓶颈会被放大很多倍,常见的 CPU 性能瓶颈有解析游戏配置文件,drawcall 等等。 另外,原本从多线程和 SIMD 获益的引擎模块,如 Skinning,粒子系统,Culling 也会变慢。

小游戏单帧 CPU 时间 10.0ms IOS 单帧 CPU 时间 3.55ms

单线程带来的另一个后果是,原本使用多线程或者 while 循环等待的地方,都会导致游戏卡死。因此需要使用协程或 C# async/await 改造相关代码逻辑。 下面是一段需要改造的代码示例:

//线程卡死代码示例
var uwr = UnityWebRequestAssetBundle.GetAssetBundle(url);


var asyncOp = uwr.SendWebRequest();
//卡死,单线程,无法跳出while循环,执行异步加载逻辑,asyncOp.isDone始终为false
while(!asyncOp.isDone);


//需要改为协程
 yield return uwr.SendWebRequest();

需要注意,C# System.Threading域名下的类和方法,如果底层实现用到了多线程,则无法使用。

渲染能力

GPU 性能对比而言,小游戏和原生 App 的差距不大,仅在将 WebGL API 和 Shader 转成平台图形API 时有少量额外性能消耗。

但在图形 API 上,小游戏只支持 WebGL 1WebGL 2,对应到 OpenGL ES 2.0 和 OpenGL ES 3.0 功能。 延迟渲染和线性色彩空间只有 WebGL 2.0 支持。

有些高级特性和优化无法使用,例如 Compute Shader。因此一些依赖于 Compute Shader 的后处理等也无法使用。

网络

由于安全问题 WebGL 不支持 Socket,因此 System.Net.Sockets 和 UnityEngine.Netwotk中的类都无法在小游戏平台使用,打包时会直接报错。基于 Socket 实现的 HTTP 请求需要替换为 UnityWebRequest,TCP 连接也需要替换为 WebSocket, 如 UnityWebSocket

更多网络适配工作请参考:

UnityWebRequest 基于 JavaScript Fetch API实现,在浏览器中运行时,如果访问的服务器未设置允许 cross-origin resource sharing (CORS), 则会报类似 “Cross-Origin Request Blocked” 的错误。需要在服务器的 http responses 中添加 Header “Access-Control-Allow-Origin : * ”。

文件系统

小游戏上无法访问设备本地文件系统,其文件系统由 Emscripten 使用 JS + IndexedDB 模拟,用户和引擎通过 WASM 读写 JS 内存中的文件,JS 层再与 IndexedDB 定期同步。引擎默认同步周期为每 1000ms 一次,因此当文件系统中的文件过多时,容易造成卡顿现象。

另外,由于 JS 中始终存有所有文件的一份副本,因此会消耗更多的内存。小游戏中切断了 JS 文件系统和 IndexDB 的同步,因此不会有卡顿问题,但额外的内存消耗依旧存在,并且游戏退出后文件不会保存下来,应该尽量避免直接读写文件。

无法访问本地文件系统的另一个后果是,原本存放在 StreamingAssets 文件夹下,可以直接使用文件 IO 接口同步访问的文件, 而小游戏中,却是在服务器端。下载这些文件需要时间,因此只能提前下载好,或者改造逻辑使用异步接口访问。

字体

小游戏中无法使用当前系统的字体,因此当未设置字体,或字体文件不包含中文字符时,将无法显示。微信小游戏平台推荐使用微信系统字体, 或使用大小在 2–3MB,裁剪后的字体文件。

平台支持
微信平台支持