我把《塞尔达传说》搬上苹果设备,「独占」还剩什么?

塞尔达传说:跨过平台的边界,Armigo 题图设计

52 个小时,两天多一点,不到三天。

我把 Switch 版《塞尔达传说:旷野之息》从 Mac 上启动、卡在系统服务,推进到了 iOS 测试版。

可这一路,画面出现了,林克却黑了;海拉鲁就在眼前,脚下的地面却没了。

随后继续优化,Mac 跑到了接近 250 FPS,iPhone 和 iPad mini 也跑到了 60 FPS。先看后来跑起来的样子。

Mac 上恢复地形后的海拉鲁

借来一座桥,还没到海拉鲁

Armigo 的起点是开源项目 d2r-mobile。它已经搭好了很重要的一段路:让 Switch 游戏的大部分程序在苹果芯片上直接执行,把系统请求和图形命令接到苹果的平台上。

这很像借来一座桥。桥不是我从零修的,但桥对岸原来通向另一款游戏,不会换个文件名就变成海拉鲁。

《旷野之息》要读自己的存档、启动自己的音频系统、发出另一套图形请求。已有的实现能接住一部分,剩下的就卡在那里。最初的启动记录,停在一个还没有实现的系统服务上。程序确实跑了,游戏还没开始。

我做的第一件事,是把这些路一段段接起来。账号和内容挂载接上了,还有声音;系统服务往前走了,显卡那边又在等。尤其是 NVN——Switch 的图形接口——原项目已经能服务自己的游戏,却没有覆盖《旷野之息》实际走到的那些用法。

从已有基础到 Armigo:接住旷野之息新增的要求

补齐也不只是让一个报错消失。一个接口终于不报错了,下一帧可能就用另一种方式调用它。绘制、清屏、纹理更新、嵌套命令,得真的做对,游戏才肯往下走。

然后,它终于交出了一张截图。

第一次保存下来的画面:一整块品红

一整块品红。

没有林克,没有神庙,连黑屏都不是。命令交出去了,截图也保存了,可这些「成功」加在一起,依然不是一张游戏画面。

神庙出现了,主角却是一团黑影

继续往前推,标题文字出现了。

启动过程中出现的游戏标题

再往后,复苏神庙出现了。到了这里,进展终于不需要靠日志解释:蓝色的光、石台、游戏界面,都能认出来。甚至已经到了可以操作林克的地方。

但主角全身都是黑的。

修复前:神庙里的林克是一团黑影

神庙进去了,林克还藏在黑影里。

看上去很像贴图没加载。真正查下去,问题却藏在光照计算里:着色器翻译时,一串条件分支丢了后半截,本该生成的环境光变成了零。

所以,不是把画面调亮就能解决。算光的那段程序得先完整地执行。

修好之后,还是那座神庙,林克的皮肤、衣着和周围的细节终于能看见了。

修复后:神庙中恢复光照的林克

光照恢复,主角终于从黑影里出来。

光照这一关过去,神庙终于有了熟悉的样子。

但室外留下的另一组画面,又把「已经搬过来了」这句话拦住了。

不是湖,是地面没了

树和岩壁都在,远处也有海拉鲁的轮廓。乍一看,画面已经很完整。可本该铺着地面的地方出现了大片镂空,林克的腿还陷在土里。

早期地形错误:地面镂空,林克陷入地面

那里不是湖面,是没有正确画出来的地面。

游戏知道林克站在哪里,画面里的地面却没有跟上。地形高度要经过一条计算和传递链路,到了兼容层,有的数据没有正确交到显卡,有的计算结果又被后续更新覆盖。于是石头还在,地形却塌了。

只盯着最后一张图,很容易以为是模型少了一块。真正要修的是中间那条路:让地形高度正确到达,并且别再把已经算好的结果擦掉。

修复后的室外场景:地形恢复,林克站在草地上

地面回来了,林克也终于站回地面上。

到这里,「能显示」和「能玩」才开始靠近。走路、转镜头、爬上岩壁,过去一张静止截图藏住的问题,会在动作里继续露出来。

操作测试中的攀爬画面

声音和输入也得接上。会动的画面不会自动带来风声,更不会因为接了手柄就自己认得按键。这些事情逐一落到实处,海拉鲁才从一张截图变回一个可以走进去的世界。

搬的不是一个旧版本,也不只有本体

还有一层麻烦,光看截图看不出来。

这次的目标是《旷野之息》1.9.0。不是沿用旧版本已经摸清的地址和处理方式,换个版本号交差。与之前准备的 1.8.2 相比,几个程序模块都已经变化,原来确认过的地方,到了新版本还得重新检查。

DLC 也不能当成「多放几个文件」。游戏得先知道有哪些追加内容,再能把它们打开,和本体、更新后的数据一起使用。Armigo 接上了这条路,运行时已经能识别两项追加内容,并读取 DLC 的版本和地图资源。

所以,这次承担的是本体、1.9.0 更新和 DLC 一起工作的那套组合。它给兼容层出的题,远不止「把开场画出来」。

Armigo 如何让游戏程序、系统请求和图形命令抵达苹果设备

Armigo 这个名字,是 ARM 加上西班牙语的 amigo,朋友。Switch 和苹果设备有共同的处理器基础,但要让它们真正交上朋友,系统和图形之间的差异,一项也不会凭空消失。

跑快了,还不能让海拉鲁快进

画面逐渐对了,接下来才轮到「快」。

不少时间并没有花在海拉鲁本身,而是花在兼容层反复准备、搬运、打断绘制上。游戏只改了一小段数据,宿主却结束一整段绘制,再从头开始。把这些白忙活减掉,速度才往上走。

可帧数越高,也越容易冒出另一个问题:画面多画了,游戏里的时间会不会也跟着跑快?

如果林克跑步、物理和世界里的钟全都加速,那不是流畅,是快进。于是还得把游戏逻辑的步长和实际经过的时间对齐。后来那次接近 250 FPS 的短测里,现实过去约十秒,游戏里也过去约十秒,才让这个数字有了意义。

从跑起来到跑得快:Mac、手机和平板的阶段结果

Mac 上省下的开销,到了手机还要换一种算法:不是只问能跑多快,还得问热起来之后怎么样。iPhone 的那次十五分钟路线测试跑到了接近 60 帧,但接电、约 29°C 的环境下,几分钟后也进入了较高热状态。帧率之外,还有功耗、散热和持续时间的账。

从 Mac 到 iOS,也不是给桌面程序换个图标。触控、声音、前后台切换,都得放进手机的使用方式里。52 小时到达的是 iOS 测试构建这个节点,后面的正确性和性能工作仍在继续。

跨过这道门之后

以前说「这游戏只能在那台机器上玩」,听着像一个天然事实。做完这段路再看,它更像许多具体问题叠出来的一道门:一处服务没接上,一条光照分支丢了,一份地形数据没送到。

把它们逐个处理,门就能打开一点。

但技术上能走出去,不会自动回答另一个问题:买下一款游戏,买到的到底是一部想长期保留的作品,还是一台机器上的访问资格?旧设备越来越难维修时,我们还能用什么方式回到那个世界?

我不想把这次实验写成「多跑了一百帧,所以平台就没有价值了」。《旷野之息》值得搬,恰恰因为海拉鲁本身值得再去一次。兼容层能把它接到新设备上,却不会替任何人创造下一个海拉鲁。

Armigo 做的,是把老朋友接过来。

至于下一位值得认识的朋友,还得有人去创造。