独立游戏爆款背后的坑 -- Unite2017-徐步诣分享《椰岛2D游戏独立之路》
编者按:徐步诣,椰岛游戏CTO,2001年入行,曾先后在上海KONAMI、上海火彩网络、AntHive Games任职,现担任椰岛游戏CTO。参与《NBA2002》、《NBA2003》、《Slient Scopt》,2004年在日本KONAMI东京总部参与 Winning Eleven Xbox主机板的制作。之后主导程序团队参与制作了原创NDS游戏 《TAO~魔物の塔と魔法の卵~》,游戏以27分的成绩入选日本《Fami通》杂志银殿堂。加入椰岛后主导完成了《决战喵星》在XboxOne和《小小白日梦》在PS4中国区的首发等工作。
关于Unite
Unite大会是由Unity举办的全球开发者大会,至今已有10年的历史。Unite现已成为游戏行业,VR/AR行业中最具有权威性和影响力的活动。
大家好,很高兴今天来到这里给大家做分享,椰岛游戏是成立于2009年,在上海是一个很小的Studio,我们致力于研发小体量的独立游戏,获得国IGF和Unity的奖项,我们每年都有组织GameJam,最早的时候是在公司里面做头脑风暴,然后我们要搞一个GameJam就搞了上海的GameJam,后来发展越来越大,就跟国际上的GlobalGameJam就发展起来,后面还有一个独立开发者社区就是IndieAce,这个独立社区规模也在扩大,方便做IndieGame的人方便交流。从2009年到现在,已经有八年,经过八年的发展,我们还是一个小公司,现在人也不多,但是其实公司的氛围很好。
我这次会说两个方面,一个是说我们作为IndieGame,椰岛和Unity是怎么发展,去做游戏这件事情。后面第二个阶段,我会分享两个简单,从我们自己项目的事例上面做一个简单的技术分享。
一、为什么选择Unity?
一开始我先多罗嗦一下,为什么选择Unity,有这么几点:
一、上手容易,这一点应该大家都会比较有体会。我最大的感觉是说Unity其实不是为了技术人员准备,其实是给策划或者美术这些人准备的,它的界面非常友好,所以一个人可以开发出一个原形哪怕不懂得写代码,在我们工作室头脑风暴的时候,基本一个人或者两个人,花一个星期就会出一个模型,我们会不停地迭代。、
二、跨平台支持,Unity5.6支持28个平台,非常了不起,所以我们其实在Xbox和PS4在国内首发的时候,也是把我们的平台植入到这个平台上,相对来说也是容易很多。
三、开发和运行高效率,其实是这样子,他们是一对矛盾,在我的理解,如果要讲求开发效率,那你的运行效率不一定很高,反过来也是一样的,但是Unity找到了一个比较好的平衡点。Unity开发效果很高,运行效率也不错,Unity还推出了一个IL2CPP技术,这个技术非常棒,可以把大部分的代码进行原生编译,保证了运行的效率;开发和运行效率是一个矛盾体,但是Unity找到了一个平衡点,这个特别好。
四、人才储备丰富,对我们小公司而言,希望进来的人,马上可以为我所用,就发挥你的能力;招Unity的人也好,或者招C#的人,都相对更广一点。
五、Editor易扩展,这是Unity的最大好处;如果针对IDE进行二次开发,需要有一个很好的平台,Unity支持IDE扩展,扩展插件可以在IDE上面跑;Unity还有一个好处,就是你的游戏在IDE里面可以编辑,可以在某种程度上做到所见即所得。
我再说一下椰岛的这个特色,其实我们椰岛基本上是专注在2D方面的,有这几个理由,还有其他的就不细说了。相对来说我们团队比较小,其实我们一个项目,大概是在人员配置一开始只有两到三个人,非常非常精简。做2D游戏相对比较适合我们的团队,因为不需要大量的美术,或者说模型之类的,我们只要2D图片就好了,这是一个。
六、快速开发出原形,游戏开发的铁三角:策划、美术、程序,理论上来说缺一不可,但是实际上,我们做头脑风暴也好,或者说参加Gamejam也好,经常看到一个人单挑。我们希望大家组队,但是一个人单挑,这也是可以的。其实相比用其他的游戏引擎,用Unity更方便支持对游戏原形进行验证和迭代。我们其实在公司里边,出原形基本上就是一个人,完了之后,原形其实是24小时到48小时就出来了,对原形进行迭代。迭代的时间确定这个原形的玩法基本上在一周两周左右,剩下的时间其实都是在打磨,所以你可以看出Unity在这个方面,其实是很高效,非常高效的使用工具。
七、3D开发有比较优势,相对来说是一个比较,因为现在游戏都是在往3D化发展,我们这边专注在2D方面。其实做2D,有做2D的好处。因为你不需要考虑那么多的特效,那么炫的表达,你要吸引住玩家,其实很大一部分程度就靠可玩性,所谓的Gameplay,反过来就让我们更加注重游戏性,或者游戏的表达,怎么才能做出好玩的游戏,来吸引玩家,在2D的世界里面,这也是一个挺难做到的事情。
二、椰岛游戏成长轨迹
这个是我罗列了一下我们产品和Unity之间的时间线,实际上之前,在2013年之前,我们也采用过其他的引擎,我们通过反复比较还是决定用Unity来做。Unity的全称叫Unity3D,从名字可以知道他的侧重在3D方面,所以相对来说2D会弱一些。所以他们是在2013年引进这个4.3版本,不知道大家对这个版本有没有印象,我觉得这个版本很重要,在他们社区里面推出来一个2D的例子,那个例子应该是在英国伦敦,那个大本钟那边,有大作战,有几个人在那边打来打去,那个时候看了觉得不错,Unity做2D做到这样了,就是在那个版本里面,引入了所谓叫2Dsprite的东西,我们《决战喵星》也是在2013年发布,但是很可惜,Unity在2013年底,我们用的技术并没有2Dsprite,我们是用AssetStore里面的Ex2D。然后到了2014年的时候,Unity发布了4.6,这个最重要的更新是引用了自己的一个界面系统:uGUI,我们在2014年最重要的事情就是移植,其实在Unity中就是一个简单的平台切换,切完之后立刻可以在XboxOne上跑了,但是上面还是有一些小的坑要去踩,这个不细了说,但是总体来说Unity在跨平台上没得说。
接下来2014年我们自己还研发了一款拼字游戏,名字叫《SpellMaster》,我们已经尝试把Sprite2D用在项目当中,这个项目几乎完成,但是最后没有上线,没有上线的原因很简单,我们觉得不好玩,这个游戏是一个弱联网的游戏,服务器全部都准备好了,但是最后还是撤下来,没有上去。因为这是一个拼字游戏,对国人来说,不是很擅长,做起来以后,老外的反馈不是很好玩,所以就放弃了。
时间到了2015年的,Unity5.3有一个最重要的功能就是2DJoint,这个东西功能非常好,对于我们自己来讲,因为《决战喵星》的成功,我们很自然想到续作:《决战喵星2》,这个项目最后在我们的团队里面也是处于暂停状态,因为我们也觉得不好玩。《决战喵星2》完全使用Unity的Sprite2D,同时开发的还有一款是《超脱力医院》,这个游戏我们从发布到现在已经累积一千万下载,《超脱力医院》中已经完全使用Sprite2D加NGUI,之所以使用NGUI是因为这个游戏UI比较重,里面有很多UI的元素,当时uGUI,我们觉得不够成熟,因为刚刚推出不久,我们就选控件相对比较多的NGUI来做。
到了今年的话,5.6是一个大的更新,非常大,相对2D而言,功能也很多。我们今年5月5号在Steam上面推出《汐》的游戏,这个《汐》的UI不是特别重所以很自然的用到了uGUI,这个游戏在后面案例分享会详细讲到。还有一款现在正在开发的超脱力的系列,也是完全用uGUI和Sprite2D,这个我待会儿也说一下。
三、《汐》的技术分享
先说《汐》的技术分享,简单说一下。我们先看一下视频。就是从视频可以看到是一个平台跳跃类的游戏,我们内部开玩笑,这个游戏的正确打开方式,你在旁边备一个手柄和备一个键盘,因为这个游戏非常难,会难到令你抓狂,可能会把手柄和键盘砸掉。其实《汐》是一个2D卷轴游戏,使用了大家非常熟悉的Parallax Scroller,有好多层,每一层都做卷轴运动,一般来讲,层会分几大部分,不一定是这三大部分,就是近景、背景、远景这样,但是在游戏里面分的层是比较多。
实际上最早FC上面的超级玛丽也是用这种方式来表达的,超级玛丽大致关卡的样子是这个样子,这个关卡有个很大的特点,就是里面的物件几乎都是可以重用的,随着卷轴的滚动,我可以把一些重复的,像管道,或者是后面的云什么的重复地把它做出来,把它放上去就可以了。相对来说比较有规律,程序也可以很方便地来实现。
这是我们《汐》的场景,在Unity编辑器里面的场景,这还只是部分,没有完全截得下来。其实大家可以看到这个《汐》的场景是完全没有规律,不是简单的横向的卷轴就完事了,上面有高度,还有关卡的设计。我们在制作上面,基本上有一个原则,我不限制游戏Level Design设计关卡,他们可以随便在游戏里面拼出想要的东西,所以我们最终的关卡变得很复杂。
基于美术这种要求,或者说基于layout的要求,不可能做到重复性,不可能做到规律的循环。另外一个特点是场景里面的物体会非常多,截图中左下角的一个小白框,那个代表一个屏幕能够显示的内容,大家可以大概估计一下,这里面有多少内容要显示。这就带来一个最直接的问题,场景我要怎么样去动态地管理。因为我不可能一加载就把这么多产品全部加载进来。虽然说显示Unity可以自动裁掉,但是场景加这么多是不可以接受,内存也接受不了。
我们就简单说一下场景的管理方式,在随便某一个关卡里面,我们的子物体有八千多个,八千多个不是所有,只是一部分,这个图大家可以看到,在左下角有一个白框,那个白框就是一开始玩家的出现点,就是你们能看到的一个屏幕的大小。这还只是游戏的第一部分,我们会、把游戏切成好多scenes,所以第一个步骤就是切scenes,我们把游戏分成几个章节,这样切一次可以把八千多个物体切成一千多个左右。切完了之后,如果你把整个scene都拿去显示,去加载的话,还是会觉得物件太多,有没有什么好的办法,所以第二步我们做法是跟Cam做一个AABB判断,所有物件在场景里面都有自己的包围盒,都有自己的碰撞,我们camera也有自己的AABB,然后就很简单做一个AABB的测试,跟Camera相交,那就说明应该出现在屏幕上面,那就显示出来,否则的话我们就把它Detach,Detach不光是不显示,内存里是被摘掉了。
还有没有进一步优化的空间?其实是有的,你如果在场景里面对一千个物体跟cam之间做一个AABB测试,这个消耗量也是非常大的,有什么办法呢?其实有一个很现成的方法可以用,就是按照空间的关系,把物体加到四叉树进行管理,这个时候跟AABB做测试,不是跟某一个特定的物体做AABB测试,是用四叉树的节点包围盒做AABB的测试就可以了,只要跟这个节点命中、相交,下面所有子物体都显示,否则的话都不显示,这是一个以空间换时间的方法。
怎么样把物体加到四叉树里面,这个四叉树本身并不是均分的,因为场景是任意的,就是根据策划的需求,有些地方摆的物件比较少,有些地方摆的物件比较多,所以我们在划分四叉树本身的时候,并不是在整个空间里面均分四叉树,而是根据关卡的特性,物件多的地方我们四叉树分的密一些。物件少的地方,四叉树分的稀一点,放入四叉树的规则很简单,就是把这个物件的AABB跟四叉树本身做一个比较,如果命中的话就放进去。但是这里面有两点要注意:
第一点,静态的物件这么处理是没有问题,但是动态的物件不行,还要单独做进一步的处理。
第二点,有可能一个物件,会同时属于两个四叉树节点中,这个时候怎么办,我们的办法是两边都放,这样其实以空间换时间效率,也没有太多的问题。还有一点我们物件并不是简单的指一个GameObject,比如在场景里面,有一个大的机械装置,齿轮或者是其他的东西,由很多GameObject组成,我们会把这一个大的所有的GameObject包成一个碰撞盒,然后把它跟四叉树的AABB做测试,放到它的某一个节点里面去,这样相对来说比较灵活,并没有强制一定在单个GameObject这个层面上,可以根据情况自由组合。
使用的四叉树优化之后,我们一个场景的数量基本上降到了260左右,从八千左右降到260个,所以卷轴的性能提升的是非常明显的。这里有一段视频,麻烦播放一下,我们看一下四叉树的命中情况。可以看到那个树叶是动态的创建出来,是根据Cam的运动情况,动态地创建出来。
总结一下就是说,怎么样去减少这个场景里面的物件的挂载,你要减少Children的Transform数,用四叉树来管理,用AABB来进行碰撞的检测,通过这种比较简单的方法可以很好的提升整个游戏的性能。
四、《超脱力医院》的技术分享
说完《汐》,我来说一下《超脱力医院》,《超脱力医院》是一个Isometric维的游戏,有人称它为2.5D,或者伪3D,最大的特点你以45度的角去看上去,游戏的场景就是很3D化,但是实际上用的2D的面片来表达出来,地图上分成N×N的格子,每个物体都在一定的格子,就这样的。也是最简单的排序方式就是以Y轴来进行排序,所以说物体Y值的大小,就决定了里面格子的描绘的先后顺序,这是最简单的方法。但是这是有问题,因为我们《超脱力医院》里面用这个方法,但是后面发现有很多的问题,有很多坑。先来看一段视频,可以看到每一间诊室都是用Isometric拼接出来的,玩家可以到诊室里进行扩展。
Isometric有不足的地方:我们在做《超脱力医院》的时候发现有很多不足的地方。主要来说有两点,第一点就是地图编辑器,我们原来用的是第三方开源的地图编辑器,他基于WinForm,就只能在windows上运行,最终显示出来和导到Unity里面有很大不同。第二点就是Y轴排序问题,我旁边有一张图,如果按照Y轴排序的话,其实这个彩色的方块应该在绿的后面,现在反而在前面,这就是引出来的问题,我们其实本意想要表达的是在红色长条后面,但实际上是在前面。基于这些原因,我们尝试在《超脱力》续作中解决这些问题。
这是续作的改进,右边这张图是后来续作目前的样子,当然这不是最终的。我们在续作里提了几个要求,第一个编辑器肯定要所见即所得,不希望两个编辑器之间来回倒腾,这是一个要求。第二个我们支持层高,所谓层高,就是要有楼梯,要有第二层楼,整个房间,有一层两层,第三个有地图光照,有白天有晚上。
这是原来用的地图编辑器,我写了一个旧,你可以看到是用WinForm做的,首先只能在windows平台上跑,第二个有一个限制,这个地图大小不能有很大,如果要做很大,我们就做四个,把四个在游戏里面拼出来,这个制作来说,效率是下降的。第三个是编辑器独立与游戏之外,所以经常发生 在编辑器里做的很好,放在Unity里面一看,完全不是这么回事,这也是一个很大的问题。
所以我们现在把它改进了一下,改进的方法实际针对上面几点,一个是把地图编辑器直接做在UnityEditor里面上面这张图,就是我们自己做的界面,最右边是物品的列表,有一些预览图,可以放到游戏当中。好处就是跨平台,刚才也说了,做在Unity里面,就不担心在其他平台上不能运行了。这边也没有区域限制,基本上就是内存限制了,内存有多少,我们就可以用到多少,在Unity里面有施展。最重要就是做到所见即所得,也就是现在的地图编辑器是这样,在游戏里看到的就是这样。你可以很方便在里面进行编辑,然后也不需要把这个数据导出来,可以直接编辑直接用,就很方便。
另外一个问题就是所谓的排序算法,我刚才说了,排序算法最最方便的,就是根据Grid的Y值来进行排序,根据Y的大小然后来决定这个是在之前还是在之后。有几个问题,最主要的是我们遇到最主要是这样一个问题,首先两个物件蓝的和绿的,按照Y轴来说,相安无事。虽然说它们都是在同一个Y值上,我们会以一个默认的方法来决定谁先画谁后画。比如决定从屏幕的左边到右边,左边的物体先画,右边的物体后画,这样就很简单,蓝色的先画绿色的后画,互相之间没有遮挡关系所以很OK,没有关系。
第二张图,我在中间加了一个红色的物体,当然只有Grid,Y轴也是向,按照刚才的描绘顺序也是没有问题,实际上先画蓝色的,再画绿色的再画红色的,所以也是相安无事。现在问题出现了,我们画一个长长的物体,就是红色的,我把它拉长,在这种情况下,你可以看到,他们的Y轴,是红色的物体这个中心,这个中心跟上一张红色物体的中心是一样的。按照这个描绘顺序来说,因为Y值是相同的,所以描绘的顺序也是先是蓝色,然后再是红色,然后再是绿色。这种情况下,显示的很好,也没有问题。
如果把红色的这个长条换一个角度,其实对于排序来说,它没有发现任何的变化,为什么?因为红色的中心还是在那个点,蓝色的中心也还在那个点,绿色的中心也还在那个点,所以它们的值Y轴是相同,按照相同的描绘顺序来看,我先画红的,再画蓝的,再画绿的,这不是我们想要的,我们想要的是这个红色被绿色的挡住,我们想要的是最后这个图的样子。这是传统的排序最要命的问题,就是说如果所有的物件都是在一个Grid的里面,老老实实本本分分没有问题,游戏当中不可能是这样,肯定有大大小小的物体,肯定会显示的时候占用多个Grid但是中心却只再一个grid上进行计算,这样的话就会带来显示上的问题。
我们聊一下,我们怎么改进,想办法去改进,我们自己也讨论过很多的解决方案,其中的一个解决方案,把红色的物体切成小块,按照Grid。因为切成好多细分的小块,每块都有自己的Y,就很简单,就按照这个东西来进行排序了。这里面其实会有一些问题,就是说这个给美术造成一定的工作量。为什么这么讲,因为美术在画的时候,是一个思维的整体,把它从程序上硬切,切完之后再去做这件事情,有可能一时半会儿没办法理解,或者设计的时候造成困惑,这是一个。第二个很简单,你也增加程序的工作量,本来一个grid计算,现在分成十个,就多了十倍,这是一个。
我们还想用第二种方式,我们确定周围的Grid,这个红色的物体,长的物体,确定周围跟它相关的Grid,然后针对这些Grid做特殊的处理,或者做特殊的排序操作,这样也是可以的。但是这引来另外一个问题是说,你要对这些相互有影响的Grid做一个标识,或者做一个特定的偏移或者什么。这个其实对程序后面的编辑产生一定的影响。还有一个就是说,你就算用了这种方法,也没有办法去实现我们要求的这个层高,就是刚才说有楼层,一层两层这样的一个要求,所以我们最后定下来的这个方案是所谓的建立3D的包围盒,可以理解为3D的碰撞盒,用来计算最终的排序。到了这一点上,其实Grid对于我们排序来说已经不重要了,一个物体到底是不是在grid上,我们不关心。完全按照这个3D的模型,进行3D空间的碰撞计算,然后再做前后的关系的排序,这样就算是有层高,也没有关系,因为我们可以很容易地,层高有自己的3D碰撞盒,楼梯,包括第二层楼,如果是这样的话,跟第一层的做3D碰撞,也可以很容易计算出先后关系。
这个就是我们编辑器里面的,可以让策划很方便去建立包围盒,或者程序可以方便去定制三维包裹盒,就是属于地图编辑器的一部分,可以看到,黄色的那个框其实就是三维的包裹盒,下面说的值,红色的框里面,框出来的三个值,其实是真正进行3D计算的数值。但是有一点要说明,就算3D,我们用的也是一个整形,就为了方便计算,还有效率上的考虑。
这样了之后,其实很好对3D物体里面,3D空间里物体做一个排序,实际上排序还是分两种方式,一种叫全排序,一种叫做局部排序,这也是为了效率的考虑,因为物件太多,如果所有物件都做3D排序很费时间,所谓全局排序很简单,就是场景里面所有东西都排一次。全局排序,是有特定的时间才会做这样的事情,一个是游戏载入。因为游戏载入的时候把场景全部载出来,这个时候做一次全局排序。还有访问其他玩家的地图,因为我们是一个弱社交的游戏,可以看到其他的玩家他们自己的地图是什么样的,还会有一些互动,当你看别人地图的时候,我们也会进行一个全排序,相当于也是一个关卡的加载。
解锁大块区的时候,我们游戏是分大的区域,大区域解锁的时候,这个区域里面本身会有一些很大的物体,有一些很重的物体,在这些物体的描绘上面,我们也采取全排序的方式。相对来说有了全排序就会有局部排序,局部排序的概念,我只排其中很小很小的一部分。在这里我们引入了一个叫做基座的概念,所谓基座是这样,所有玩家可以修改的部分,因为我们这个游戏允许玩家可以装饰自己的办公室,装饰自己的街道这样子,摆一个路灯,摆一个花盆这样。在这里可以自定义的地方就引入一个叫做基座的概念,在这个基座上面,我们留出大概50到100个空档,这是排序的值,把排序的值留出来。在基座进行物件摆放的时候,就直接在基座的偏移上面加上,那基座只需要排一次就完了。
最重要的东西就是移动的物件,如果所有东西都是静态,只要排一次就完事了,就很完美,但实际上里面有人走来走去,人会跟静态的物件之间发生排序关系顺序的一个变化,这件事情怎么解决呢?我们的做法是说,所有在游戏里面移动的物体,都是用路点控制,实际上不可以随便乱走。路点本身是一个3D的点,其实是跟刚才的物件的包围盒在一个坐标空间里,我们只需要对路点和包围盒进行排序,能确定前后关系。另外一个问题我走在两个路点之间怎么办,就走一个线性差值设置就行了,也可以比较好的解决这个问题。
我们楼梯其实已经做了,但是移动的那个人,动态的物件我们还没有加进去,我们现在一个空的场景。然后这个是白天到晚上的切换,我今天分享就到这里,谢谢大家。