引子
几个月前,我接手了一个C++老游戏(以Cocos2d-x为例),目标是上H5平台和微信小游戏。
研究了一下Wasm的可行性,心里大概有了底,就开干了。刚好Cocos2d-x社区有一个专门支持Wasm编译的分支,就基于这个分支开始折腾。
照着GitHub上的README操作,主要就是三步:安装emsdk、编译、跑通Demo。看起来不复杂,跑起来也确实不复杂——跑通Demo不复杂,但跑通一个真实项目是另一回事。
一、真正的问题,都在Demo之外
照着仓库操作,跑通官方Demo没问题。但对于一个复杂的网络游戏来说,就没那么简单了。
先说点基础背景。Wasm是一种可移植、体积小、加载快的二进制指令格式,运行在独立的虚拟机沙箱中。浏览器加载 .wasm 后,会进行解码和编译,生成机器码,然后在沙箱里执行。
C++代码通过emsdk编译成.wasm,并且可以和JavaScript交互。工具链已经集成了一些常用库的支持,比如WebGL、WebSocket等。编译OpenGL代码时,会自动生成JS胶水代码来和Wasm虚拟机交互。
这里有一个核心认知需要建立:Wasm本身不提供任何系统能力,所有原生功能的实现,本质还是依赖浏览器提供的能力。 比如OpenGL → WebGL,Socket → WebSocket,文件访问 → IndexedDB/微信文件API。
如果某个功能在浏览器上没有对应的实现,那这条路就走不通,或者需要你自己找替代方案。
下面是我在这个项目中遇到的主要问题(以Cocos2d-x为例,但大多数C++老游戏转Wasm都会遇到类似的问题):
1. 网络问题
原生网络库(比如Berkeley Socket)在Wasm里编译不通过,因为浏览器根本不支持。解决路径比较清晰:服务端需要支持WebSocket。如果服务端暂时改不了,可以先用中转服务做适配,先让客户端跑通。客户端这边,就是把原来的网络库代码,按emscripten提供的WebSocket库进行改造。
2. 文件系统问题
文件系统在Web端和小程序端表现完全不一样。
Web端不能直接访问本地文件,同步接口只适用于内存文件系统——也就是把整个文件载入内存再读。这就涉及一个权衡:内存文件的大小直接决定了内存占用,载入太多文件,内存可能直接爆掉。具体怎么拆解,得看游戏本身的资源管理逻辑。
微信小程序端则依赖小程序自己的文件系统API,默认给的是200M存储空间,需要结合游戏逻辑去规划:哪些资源走本地存储、哪些放内存、哪些放到临时目录、怎么管理生命周期——这都是需要提前设计的。
3. 文件下载问题
原生的下载接口在这里基本都不适用。比如小程序的下载要走wx.downloadFile之类的接口,需要针对不同平台做适配改造。
4. 渲染问题
有些OpenGL的高级特性可能需要裁剪,或者需要开启WebGL的完整支持。这块没有统一答案,基本是按编译报错和渲染表现逐个排查解决。
5. 其他原生库问题
项目里用到的第三方原生库,有现成的Wasm版本就直接链接,没有的话就要评估:要么找对应的JS实现替代,要么自己移植。
6. 输入问题
原生输入控件在H5上不适用——Web上一般用Input控件,微信小程序又有自己的一套API,都需要分别处理。
7. 音频问题
和输入类似,小程序的音频需要按微信的原生接口来改造,不能直接用原生的音频库。
8. 线程问题
如果不是特别需要,建议关闭Pthread,不要硬上多线程。Wasm的线程依赖SharedArrayBuffer,浏览器和小程序都有严格的限制。如果确实要用,按微信小程序的Worker规范去实现,同时要注意数据传递的方式,否则可能比单线程还慢。
9. 内存问题与性能问题
这两个是贯穿始终的老大难,也是最有技术含量的部分。这块内容比较多,后面再展开说。
小结
以上只是把主要的问题域列了出来,实际做的时候,每个点下面都还有无数的细坑等着你。
除此之外,原生游戏本身也要做改造——至少要支持WebSocket,并且支持从远端动态下载游戏资源、正确处理内存释放。否则大型游戏在纯Web环境下根本跑不起来,内存那关就过不去。
参考资料
- Cocos2d-x 社区维护分支(基于 3.17.2):github.com/WuJiayiSH/c…
- Emscripten SDK(本文使用 3.1.10):github.com/emscripten-…
一个专治C++老游戏Wasm移植问题的老兵。编译卡住了?平台适配没头绪?内存优化搞不定?欢迎私信交流。