天神互动是一家已经上市的以研发MMOARPG游戏为主的技术公司,《苍穹变》是天神互动研发的第一款3D游戏。这款上线于2013年7月的产品,目前依然保持着腾讯游戏大厅单款产品单平台月度流水最高的记录。本文将分享《苍穹变》是如何利用Unity开发引擎打造RPG游戏的。
Unity的利与弊
一提到MMOARPG游戏,大家首先想到的就是体量大。以《苍穹变》为例,仅客户端的代码厚度就多达40万行以上,开发团队也从最早的十几人逐渐提高到了50人。MMOARPG游戏有一个特点——实时性比较突出,比如我正在做一个任务,说不定突然就会有几个玩家身着不同的装备,骑着不同的坐骑,拿着不同的武器出现,这瞬间对你的电脑性能要求非常高,不管是CPU、GPU,甚至包括网络带宽都会产生瓶颈,如果处理不好就会造成卡顿或崩溃。
因此,我们选择用Unity作为开发引擎,优点包括以下几个方面。
Unity是使用高级语言编写的项目,包括Unity在内的所有引擎都是用C++开发的。
出于兼容性和运行效率方面的考虑,很多引擎要求客户端也用C++来开发,这对前端程序员的要求非常高。因为相对于C#语言来说,C++是一种比较难掌握的语言。C++的程序员如果写出不符合要求的代码,崩溃之后不好查问题,有Dump文件都没法定位。而C#则无需开发者管理内存对象,统一由Unity进行回收。一般来说,如果单个文件的大小超过4000行,会被认为代码可读性变差。但Unity可以很方便地通过组件式方法把一些常用的功能分开,整个精灵类只要管理好这些组件进行协调合作就可以了。
作为一款较为成熟的游戏开发引擎,Unity几乎包含了传统开发3D ARPG游戏常用元素最好的解决方案,不管是从地形上,还是动画和UI上都能找到你所需要的模块。
Unity的跨界合作思想推动了插件市场,这在很大程度上缩短了游戏开发的时间,因为它使一些基础的工作变得模块化,一些成熟稳定的插件还可以有效降低成本。NGUI插件就是个例子,如果让我们自己的团队去开发UI引擎的话,造成的时间浪费是不可想象的。
当然,这样一款结构庞大的引擎,也存在一些缺点,其中最大的一个问题就是内存不易管控。上文说到C++是自己管理内存的,只要管理得好就不会出问题。而C#则是由Unity来管理内存对象的,如果写代码时不注意,某一个对象在它没有用的时候仍被引用,那么Unity在回收内存对象时不会对其进行回收。我们是从2.5版本时开始使用Unity的,那个时候以及之前的版本还不够稳定,出现问题也不好解决。Unity会经常崩溃,但崩溃原因却无从查起,影响了开发进度。
另一个问题在于,刚才提到了组件结构,如果只是几个组件还比较好管理。组件多了之后,每个组件都有访问当前对象的权限,这时如果同时调用组件肯定会产生冲突,一定要基于一个很好的架构支持,才能有效避免组件之间的冲突。
最后,我们知道Unity能输出Web版应用,这是Unity非常强大的功能,但再怎样,Unity也避免不了插件的问题。除此之外,Web版还存在缓存、更新等问题,几乎任何一个Unity开发的Web版游戏都要求用户下载微端,大家在决定开发Web应用或游戏时,需要考虑到这一点。
MMOARPG游戏的团队管理
开发这样一款游戏,团队协作无疑是非常重要的,这一过程也免不了会遇到一些问题,在此我给出几点建议。
要切记在合作中会相互产生影响,比如某个程序员不小心将自己编写的错误代码提交到其他版本服务器上,这很可能造成别人更新后进不去游戏,进行中的工作也就被打断。这需要建立一套严格的自检制度,在提交代码和资源时,一定要进行严格的自检查,确保没有问题后再上传。我的建议是,要做好版本回退的工作,一旦版本出现问题,可以退回到上一个版本,保证其他人员可以正常工作。
团队要重视错误日志。首先你要对Unity输出错误日志进行拦截并显示出来,在内网开发时,如果出现问题,QA很快会告诉你,除非你的日志输出不够明确。在开发《苍穹变》时,QA帮我们的程序员找出了很多代码上的错误。但一旦版本发布到外网,出现问题时,玩家是不会帮你提交Bug的,这势必会造成玩家的流失。所以我们要建立一套日志上传系统,万一出现错误信息,可以将它上传到服务器上,让程序员来修正。
分工明确,责任到人。某些工作可以几个人同时完成,这时要指定好责任人,一旦出现问题,QA可以第一时间找到责任人,让他来修正,避免出现扯皮的情况。
重视培训。一个团队能否坚持培训,并把培训做好是团队是否成熟的标志。培训表面上看跟开发游戏没有太大关系,而且耗时耗力。但实际上,它却可以让新入职的员工避免前人犯过的错误,也可以让培训者对自己教授的内容有更深刻的体会和认识。这总体而言是有助于加快项目开发进度的。
最后是重视代码的可读性,我提醒程序员,写代码时要牢记,你的代码不是给自己看的,而是给整个团队看的。如果你写得乱,过段时间自己都看不明白,更何况其他人。万一别人接手你做的模块,看懂这样的代码比重写时间都长,这是对资源很大的浪费,开发者需要引起重视。
图1 天神互动内网发布流程图
除了上面几点需要注意外,建立一套快速开发的流程也十分重要,我认为一套扁平式的开发流程可以很有效地控制沟通节点。如图1所示,这是我们内网的发布流程图,任何一个团队成员,不管是程序员还是策划,都有把自己的工作内容提交、发布、出版的权限,经过严格的自检查,就能把自己完成的功能发布成一个内网版本。比如一位美术做了一个特效,检查无误后,可以将这个特效替换内网版本本地资源文件,进游戏后就可以确认资源是否正确,如果没有问题即可正式提交。如果他不确定是否有问题,则可以请QA来协助测试,QA会对整个内网版本进行掌控,并将出现的问题反馈给相应的工作人员。
Unity是一个集成的大开发环境,这个环境与我上面这一思路是有冲突的,我不认为大集成开发环境适合团队协同合作。我们的特效、角色、场景等编辑器都是单独拆分的,包括客户端、版本发布模块等访问一个公共资源目录,美术、程序、策划输出资源到这个公共目录,版本发布模块从这个目录取资源然后再发布。在《苍穹变》,Unity项目的客户端分为逻辑开发组和引擎支持组,成长线、业务逻辑由逻辑开发组负责,优化、工具编写、维护网页版插件、Launch等以及外围功能开发工作则由引擎组来负责。其实他们的工作都是属于客户端,但在职能上差别却很大。像《苍穹变》这样的游戏有170多个窗口,每个窗口都需要逻辑代码支撑,本身工作量就非常繁重。如果这几个程序员在书写业务逻辑时,还要制定发布流程及其他一些工作,可能根本顾不过来。所以我们专门设立一个引擎组来进行库底层架构支持,还有一些性能优化模式,以及技术创新加入到游戏中来,这些做法都是为了能更好地分工。
微端游戏的优化
作为一款微端游,肯定面临一些网页上的问题,我相信这些问题其他使用Unity3D开发的应用也会遇到。
关于插件的下载和更新。2013年,Unity插件与国内服务商开始合作,现在下载插件基本上不是问题,但每次更新插件,因为服务器不在国内,会经常出错,导致很多玩家流失。为了解决这一问题,我们在网页里加入了一个计时器,在进入游戏超时后会由代码来刷新网页,很大程度上解决了更新的问题。后来我们干脆使用了自己的插件,从而在根本上解决了以上问题。
关于优化。我认为游戏开发者对于性能优化这块不能忽略,不管未来硬件有多大提升,性能优化的话题永远都不会过时。如何做好性能优化?一句话:够用就行。如果512的贴图能用就不要用1024的,使用变量的时候Byte能用就不要用Short。比如大家都比较困惑的贴图问题,到底是大图好还是小图好?根据我们的经验,在正常视角下,一味追求大图不但不能提升画面效果(显卡会将其自动缩小),反而会降低GPU运算能力和画面品质。在微端游的开发上,有些功能或资源,如果觉得有没有都行,那最好是砍掉。这方面还要做好资源的复用,比如你做了一匹狼,把这个狼放大就是大狼,缩小就是小狼,换白色贴就是白狼,换黑色贴就是黑狼,这样可以有效地节约资源。此外,贴图要做成2的幂次方,尽量使用压缩的贴图格式。还有就是Update函数每一帧都去调用,GameObject.Find是一个公认的比较费的方法,就不要在这个函数里使用,建议把一些真正需要缓存的数据提前缓存起来。还有一些关于场景的优化方案,作为3D游戏,由于远小近大的特性,一定要启用LOD和MipMap。如果是全3D的应用,建议启用遮挡剔除,这对于减少渲染压力还是很有效果的。从渲染和引擎的工作原理来说,尽量对场景物件通过Shader进行从近到远排序,我不太建议使用双面材质,尽管它看起来省面,却会影响光照,如果使用不当,比如一个球形的双面材质,里面可能永远看不见,但显卡依然要对内侧进行渲染处理。
关于特效,只使用一个粒子的特效可以用模型片来替代,这样做可以很大程度上节约内存。我们默认放一个粒子到场景中,默认最大粒子数是100个,这个粒子需要多少片就设定成多少,多了也是浪费。在动画方面,用Unity进行动画编辑实际上很占内存,所以还是建议通过脚本来实现。
关于编写代码。首先要避免直接持有对象的引用。此外,if语句对于else语句的处理要格外重视,大多数的Bug都是因为else没有处理好导致的。函数名及变量名尽可能使用一套便于查询和理解的命名法来命名,函数不要中间Return,最后单个类不要太大,这会影响代码的可读性。
要运用换装系统,如果玩家已经有了一款装备,该装备还有外显,玩家会很乐意为此付费,毕竟他可以拿来炫耀。而且换装也是一种能实现游戏资源复用的有效方法,既丰富了场景也节约资源。换装分为蒙皮换装和非蒙皮换装,蒙皮换装是根据骨骼信息,从角色身上找到相应的骨骼,然后把蒙皮绑在骨骼上,再把既有的蒙皮删掉。非蒙皮换装是指对于武器等一些挂件,把新的资源模型挂载到要绑定的骨骼上。《苍穹变》是一款强调动态加载的游戏,不是一次性将游戏场景全部加载出来,而是动态性地加载。换装系统与动态加载技术不谋而合,可以说是双赢的举措。此外为了换装,我们把蒙皮换了好几个组件,会增加一些Draw Call,如果换装完成后把这些模型进行合并,可以有效降低Draw Call。
关于场景资源管理,如上文所说,场景要分批次加载,因为现在的玩家耐心越来越差。要做好这一点就需要分清主次,做好分级处理。比如地形是首先要加载的,其次还有天空盒,最后再加载一些零碎的小物件。
物理效果可以提升用户体验,增加游戏打击感,但由于网络带宽及服务器同步的限制,只能在不影响到同步的情况下使用,比如角色死亡,头发、衣袖的摆动效果等。但物理引擎比较耗费资源,建议酌情使用。
最后我还想说一下微端技术方案OTA(Over-the-Air Technology),虽然Unity自身支持通过3W进行资源下载,但可以的话,我建议开发者尽量自己去开发一套下载组件或工具。因为Unity本身要处理的事情非常多,如果使用第三方工具或者组件,可以缓解Unity运行压力。