概述
自己对于Unity开发,特别是基于Unity的手游开发仍在学习当中。在玩mltd的同时,也有从开发者角度对mltd进行观察,收集情报,以及一定程度的拆包,以期从mltd这个项目中学到一点开发相关的经验。同时也部分了解了mltd的素材资源获取机制,尝试用c#写成了一点小工具获取和处理游戏资源,在这里一并进行经验总结和讨论。
版本之争
开发之初,mltd团队采用的Unity版本为5.6。从内部版本号14585开始,Unity版本被更新到了2017.3。时间是在今年4月。
一般来讲,在项目中途更改Unity版本并不是一件特别提倡的事情。不同版本间API的实现和调用情况可能会有不同,整个工程转换后版本容易引发各种预料不到的问题。今年5月的Unite Tokyo 2018大会上mltd团队分享开发经验(就是akane大作战那次)的时候,在PPT里面写道「游戏发布之后再对Unity版本升级相当不容易,慎重!」 ,想来恐怕也是踩了些坑。(笑)
不过自己印象中API改动较大的版本是在5.4左右,5.6之后相对来讲问题不算太严重。况且2017系列版本新加了不少新功能,进行升级确实有一定的必要性。举个例子,2017版本有了更好的AR支持。而mltd就实装了AR功能,并在一周年活动和感谢祭活动期间开放AR供玩家使用。因此倒推回去猜测,Unity版本升级或许就是开发团队在部署这样的考量和布局。可以认为升级Unity版本既是开发者最适化自身开发环境的需要,也是mltd本身不断进化,不断提升品质的基础。
服务端
客户端开发采用了谁都知道的Unity,而服务端方面,BNSI的选择则是谷歌全家桶(误)Google Cloud Platform,应用了其中Google App Engine、 BigQuery、 Cloud Datastore等多项服务,还使用了Go语言,在搭建服务端功能、获取数据分析等服务的基础上,实现了动态调配服务器资源以适应突发高频率访问等情况,同时保证服务端数据能够被高速存取。技术细节自己并不是特别熟悉,更多内容可参考这篇实质GCP广告(x)的Blog进行了解。
服务端提供的另一个功能,是为玩家提供游戏资源文件的下载服务。同本世代许多其他的手游一样,mltd的安装包进行了足够的精简,只保留了游戏运行所需要的最基本的素材资源,其他许多内容则是在需要用到的时候,再从服务端下载获取。具体的实现机制后面再细讲。
下载的过程中总会有个进度条,因此游戏也被一些玩家称为“读条大师”。自己倒是认为,这至少也比过去那些先下载几个G的资源,然后才能开始玩的手游要友好得多。当然,在进入游戏后自己能选择一次性下载完所有资源,能以后不用再读条的话也是个选择。mltd实际也追加了这个功能,不过实现得有些迷,在一些地方仍然会读条下载。似乎这个一键下载功能没能够把所有资源都下载下来,效果不是特别理想。
安装包瘦身
随着一款游戏在运营过程中推陈出新,游戏本体和安装包的容量都会变得越来越大。尽管说现在都8012年了,不会有人心疼多出来那么十来MB的流量就取消下载拒绝一款游戏,但是能够压缩的资源就尽量压缩,能复用节省空间的就尽量节省,多抠细节总不是坏事。
这是手头一点并不完整的mltd各个版本安装包的列表。虽然样本不够多,但大致能够这样认为,在安装包随着版本号的升高而不断臃肿的同时,开发团队采取了行动,对安装包进行瘦身。瘦身的方式是,将原本一些封存在安装包中的资源素材挪到服务端上面去,需要用到的时候再进行下载。具体挪了哪些素材,一一列举出来并没有什么意义,不如讲一个事例。
可能有同僚会记得,一周年活动结束之后的一段时间里,mltd偶尔会出现这样一个BUG。在剧场中点击“设施移动”,显示出四个房间供玩家选择的时候,如果某一房间内有可以互动的偶像,那么该房间选项的右下角会显示该偶像的头像,头上还有一个“…”的气泡。而出现的BUG则是,偶像头像消失不见了,只剩下头上的气泡。出现这个BUG的根本原因,正是小偶像们的头像文件,被从安装包中挪到服务端上了。而直接原因,可能是网络状况不良或者下载过程中出错,或者相关逻辑实现得并不好,没有对头像文件进行预载等等等等。总之,这个BUG算是安装包瘦身所产生的一点小小的副作用。想来头像这种需要立即使用,等不及慢慢地请求服务端响应再慢慢下载的东西,恐怕还是放在本地随取随用比较好。
图像资源
先请允许自己怀个旧。以前一段时间曾折腾过CS所用的Quake2引擎(以Valve自家的叫法则是“GoldSrc”金源,是起源引擎的前身),借由其关卡编辑器制作CS的关卡地图。这个上世纪末诞生的东西对于导入引擎的贴图素材有着严格的限制。图片必须使用BMP格式,24位,256色。alpha通道自然是没有的,要想贴图带有透明镂空效果,得把透明部分填充成 RGB 0,0,255 的纯蓝色(和BMBB素材同理)。甚至图片尺寸也限制为必须是2的次幂,比如单张贴图得是256x256或者512x512,即使贴图实际上不是方形,也得先按这个尺寸做,再导入进编辑器进行平铺或者拉伸。而现在都已经8012年,主流引擎早已没有这些奇奇怪怪的要求了,各种常见格式的图片都能够很好地支持,尺寸方面自然任意大小也都能够导入。
不过从优化的角度,为了便于各类图像压缩算法对贴图进行压缩处理,仍然可以将图像尺寸限定在2的次幂内。并且包括Unity在内的主流引擎也都支持图集功能,将多张尺寸较小的素材图片放在同一张图内,导入进引擎之后再分别拆分出来进行使用,既能够高效压缩图片,又能减少使用时读取资源文件的次数。
mltd的各类素材资源基本为1024x1024或512x512。稍小一些,不适合拆分拼接的图像,也有使用512x256等比例。按钮、窗口等细小的UI元素直接放到一起拼装成单个图集,2d背景、卡面立绘等实际为矩形且尺寸较大的图像,则在拆分之后拼成规则的正方形贴图。
资源索引
前面也提到了mltd的游戏资源机制。多数素材资源存放在服务器上,且通过Unity拆分为了许多个单独的AssetBundle。在游戏过程中需要用到某个资源时,客户端先检测本地是否存放有该资源文件,版本是否为最新,不是则向服务器请求进行下载。
为了实现这个机制,客户端中使用一个索引文件来存放所有资源文件的信息。就像是一个商品目录,里面记载了所有商品的名称、描述和编号。使用者根据名字找到想要的商品之后,将其商品编号报给商家,就能坐等着商品配送到自己手中了。之后每次版本更新,加入新的素材资源的时候,相应地让客户端获取最新的资源索引文件,从而确保客户端随时可以下载到任意需要的素材资源。
再具体地讲讲这个索引文件。
截止目前2018年11月31000版本号为止,索引文件中记载的资源文件数量已经有了21648个。为了能够高效地传输、读取这个含有超过两万个对象,接近二十万行的索引文件,mltd选择了MessagePack。详细信息可以从msgpack的官方网站中了解。简单地讲,msgpack是一种高效的二进制序列化格式,通过牺牲文件的可读性,换取了更高的压缩比率和读写效率。
不像json或者XML那样随便用一个文本编辑器打开就能够浏览其中的内容,msgpack打包的文件直接打开只能够看到压缩后的一堆16进制数字。而相对应的,它所占据的空间大小也要比json和XML来得小得多。自己写了个程序将msgpack转成json,尽管不是很标准很美观的json格式,也能够对比出差别了。以31000版本为例,msgpack序列化(后缀使用了data)的文件占据空间为2.62Mb,而转换成自用json格式之后占用空间就达到了3.81Mb。这还是自己弄的,符号比较密集不太规范的json。如果放到json viewer中将其标准地格式化,为了最佳的可读性再塞一大堆换行符和空格进去,占用空间则会达到6.87Mb。相当可怕了。想象一下如果没有使用msgpack格式的话,刚启动游戏读取黄色进度条的时候,需要额外花费多少时间才能下载完这个索引文件。
自己惯用C#的缘故,主要使用这一个SimpleMsgPack库。非常小巧、方便,而且提供了不少示例代码以供快速掌握msgpack的使用方法。在这里推荐。
接着来看索引文件转换为json之后的样子。这里截取了一节,也就是一个资源文件的部分,为了便于说明去掉了json格式所需的各种符号。
1 | 001har_sign_ssr.unity3d |
第一行是文件名称。001har表示是春香阁下,sign_ssr说明是阁下SSR卡面所用到的签名的图片素材。随后是文件后缀名”.unity3d”。这个文件名称比后面几行的数据高一个层级,可以理解为它是类名,后面是类中的属性。
第二行是一串40位的16进制数。可能是以某种方式加密的字符串,不过自己对相关内容不了解,没有破解的思路。值得注意的是,在安卓的应用资源目录(Android\data\com.bandainamcoent.imas_millionlive_theaterdays\files\Cache00\)中找到mltd的缓存文件,其中该例中的阁下签名文件在其文件名末尾塞进了这条数据。也就是说,除在很小的可能性下这个文件在未来哪次更新中进行了修改的情况外,这个文件在手机存储中的名字会一直表示为001har_sign_ssr.unity3d.c375f894959830216967155f37c4a3fa589cfd92。这样的做法,猜测是检测文件是否有更新,或者从安全角度考虑检测文件是否被篡改的目的。
第三行看起来也像是另一行加密后的数,不过注意到其末尾有一个”.unity3d”。因此推测出这是资源文件的URL链接的末端。前面加上万南服务器域名目录等前缀,就能直接通过网络下载到这个资源文件。具体的域名太长这里写不下就不写了。NeoDownloader中就是以这个原理,先确定版本号,获取对应索引文件,然后从中查询到所需文件的url,通过HTTP协议下载该资源文件。
第四行是一个十进制数。通过对照手机中的缓存文件很容易发现,该数值是表示这个文件的大小。单位为字节。如果文件在新版本中有更改,那么其文件大小必然也会有变化。因此可以通过这个参数来检验文件是否有改动,而且应该会比使用第二行的16进制数更加高效方便。NeoDownloader中的版本对比功能,就是以这个思路实现的。
网络连接
mltd可以称作标准的“弱联网游戏”了,不需要时刻保持与服务器连接的“在线”状态,也不需要向服务器发送心跳响应。即使挪到后台,不管放置多久,再开起来依然能够继续游戏,不需要重新登录。(当然跨天、更新之类的回到标题的情况不算啊) 这也是目前市面上许多非实时对战类型手游常见的做法。游戏基本逻辑交给本地,每当进行涉及到服务端功能的操作时,才会向服务端发送数据进行交互。在交互的同时还要进行各种检测。整理起来大致的逻辑有:
- 检测APK版本是否有更新
- 检测游戏版本是否有更新 / 检测是否有新文件下载
- 检测时间是否为新的一天
如有符合检测条件的情况,则会回到标题页面,直至通过相应检测之后才能再次进入游戏。
前面提到了HTTP。mltd中也有大量使用http协议进行通信,并且有情报指出mltd采用了BestHTTP插件。这是一个挺有名的底层通信插件,通过Unity资源商店即可获取(60刀,一个毫无折扣的3A大作的价格了=. =),在中文圈内也能找到不少文档介绍和教程。自己对其了解还不够多,接下来也准备多研究尝试这个插件。
其他方面的内容以后有更多情报再进行补充。3d方面接触尚少,目前还没能有拿得出手的,能分享的东西。还得继续学习。文中表述不严谨、有问题的地方欢迎讨论和指正。权当抛砖引玉,期望能有更专业、更熟悉的大佬进行指导。
提到的一点小工具放在了自己的Github上面。其实都不是什么有技术含量的东西。全是别人API的调用罢了,许多地方还实现得很不好。同样希望感兴趣的同僚一起进行讨论。
虽然文中涉及到了一些拆包相关的内容,但讨论拆包并不是这里的主旨。自己还也没有那个能力进行卡池预测或者玩黑科技(嘛…从资源索引那里倒是有些思路就是)。况且拆包这种事也不适合到处传播。这里的主要目标仍是从开发者一侧进行考察。作为一名司机,学一点汽车原理,学一学汽车部件拆卸和维修,还是为了对汽车能有更好的理解,能够安然处理驾驶时遇到的各种问题,更好地开车。