5 月 28 日,云畅游戏 CEO 高云峥和 Unity 的高级解决方案工程师李中元两位大咖进入了 Unity 直播间,为大家带来了技术分享。大波粉丝提问之下,问答区共计收到了 50 多个问题,还挖出了不少制作幕后。
本文精选了本次线上分享会的精彩内容,欢迎大家前往 B 站观看完整版https://www.bilibili.com/video/BV1M54y1572J。
基于Unity的改进与定制实现Console级的动作手游
大家好,我是云畅游戏的 CEO 高云峥,可以叫我 Bobby。今天我演讲的主题是《基于 Unity 的改进与定制,实现 Console 级的动作手游》。
首先,我们公司非常荣幸在 2016 年开始获得了 CAPCOM 的授权,开始以《鬼泣》为 IP 开发手游,就是《鬼泣-巅峰之战》。这款手游我们历经 3、4 年的时间,与 CAPCOM 一起打造了一款真正意义上的《鬼泣》空战手游。
我先从整个产品的特性开始。首先大家都知道《鬼泣》这个 IP 是一款非常经典的主机 IP,它从 PS 一代开始一直延续到 PS 五代。鬼泣的作品也从一代一直到五代,包括各个特别家庭版,包括 DMC,每一代的作品动作片,基本上都是天花板的水平。当我们想要把这款游戏带到手机上来的时候,我们首先要借助一个很好用的工具。我们在评估下来之后发现 Unity 是一款非常合适的引擎,去帮助我们在手机平台上打造鬼泣。
下面就以几点主要核心的说明,具体阐述 Unity 的表现能力以及在《鬼泣-巅峰之战》研发中的应用。
01 头发的制作
首先我们讲头发。为了表现更柔顺的效果,我们使用了两个 pass 进行渲染,同时,我们把头发分成了内层、外层和修饰层三个方面。左上角的图在做三个方面的切割。
内层是我们防止穿模的层,帖图几乎是不透明的;头发的外层是我们在做但丁基础的外形,这个主要的目的是角色设计;最后一层是修饰层,主要是为了修饰外形的,主要是为了增加一些细节,我们看到的头发的一些撅起的部分,一些发梢刘海的效果,都是我们修饰的效果,这个是为了让头发显得更加生动,因为贴图发量很少。
02 动作系统
我将从三个方面阐释《鬼泣-巅峰之战》如何实现在战斗中的酷炫效果。
首先,流畅舒适的动作感受必不可少。为了达到流畅且合理的动作效果,我们将它的速度设为渐变,同时配合我们的 BlendTree 设置,使得动作实现流畅的变化。再辅以其他合理的机制,比如在加速跑到待机的过程加入对应的急停动作,使得《鬼泣-巅峰之战》的动作感受达到整体的舒适和统一。
此外,我们还对复杂动作进行了动作分层的处理,为了在动作切换的时候不是太过僵硬。比如但丁开枪行为展现的同时,也会接收玩家发出的移动操作,这种复杂动作我们就要采用更复杂的动作分层相关技术。我们将上半身抬手进行射击的区域,与下半身双腿移动的区域进行分增与融合,结合玩家输入型反馈,最终实现一整套复杂的动作行为,通过分层的方式我们对于复杂的动作进行解构。
最后,在动作切换方面我们也下了大功夫。我们把不同的动作进行划分,可以划分为基础动作和行为动作,基础动作是指人物的待机、移动、转向,行动动作指释放技能、受击、与场景交互。基础动作来说,任何动作的起点和终点都是有待机行为的,这个是有要求的,不能有特别大的动作,移动是要有速度,前面的 BlendTree 能够生效,他们整体的规格务必要一致,比如说人物先迈左脚还是右脚的问题就必须要纳入考虑了。
而对于行为动作,我们又可以进行二次细分,分为主动行为动作与被动行为动作。顾名思意,主动行为就是由于单位自身上主观意愿进行的动作行为,比如释放技能,被动行为就是由其他单位导致自身进行的动作行为,比如受击。下图为某个切换过程的融合配置:
高云峥老师还分享了《鬼泣-巅峰之战》是如何制作角色表情、角色 AI、关卡、后处理优化等内容,尽在 B 站完整录播中。
《鬼泣-巅峰之战》精选问答
本次出镜答疑的,除了云畅游戏CEO 高云峥,还有《鬼泣-巅峰之战》的制作人周辉和技术负责人缎君衡。
作为《鬼泣-巅峰之战》的首款手游作品,《鬼泣-巅峰之战》是如何尽量还原主机端游的触感的呢?
周辉:这个问题我们拆开两块来讲。首先说一下原版的操作方式,原版是可以通过手柄或者是键盘来获得按键反馈,这样玩家很清楚知道我的按键是什么,实现了什么操作。然后,我们在《鬼泣-巅峰之战》里开发了这么一个锁定的功能。通过锁定,通过推摇杆前后配合组合按键实现连打的操作。
其实这种手动锁定的方式在手游里用的比较少,我们在最开始制作的时候也是没有制作这个操作方式的。随着开发版本的迭代,我们主动加入了锁定的方式,这也是我们在手机上实现的比较有特色的点。通过不同按键的组合方式,去替代原版的手柄操作,锁定+前后推遥杆的组合按键,在这个上面降低玩家的操作复杂度,同时也保留了原版所有的技能和招式,玩家在手游里也可以做实时连打等操作。
高云峥:我也帮周辉扩展一下,因为《鬼泣》是一个传统的端游作品,端游共有12个按键,所以我们在手游研发时做了大量的工作去简化,这不仅是策划的工作,技术的同学也参与其中,包括适配手柄等,这样大家既可以在手机上体验,也能够连接手柄获得更好的体验。
空中战斗动作设计方面,与地面战斗有什么不同的地方?
周辉:这个问题问得比较专业,对于一个动作游戏来说,在空中和地面其实在动作上是完全两种制作方式。如果说传统我们在地面上打击和打击反馈都是人在地下,我双脚是在地面的,我攻击都会伴随一些角色往前走。所以我前面看到无论是挥刀,还是做一些射击的动作,通过这种方式来体现角色的打击感,这个对打击感的帮助也是很大的。包括受击一方也会给玩家带来一个比较真实的受击反馈。
但如果是在空中,首先角色在空中很难前后的移动,因为你更多还是基于上下的位移,同时我们所谓的怪物在空中的受击也跟地面完全不一样,最重要的因为是我们要在空中还原一个比较真实物理反馈的感受,当我在空中跟怪物发生攻击之后,比如说有击坠,或者有再次击飞,这块就需要我们的物理算法能够达到一个相对真实的反馈体验。然后包括很多怪物从空中到地面,再从地面反弹起来的展现的形式也是需要我们去单独的处理的。
所以,空中的话实际上会比地面在打击反馈上更难以处理,包括在比如说我使用一个连打型的武器,全套在空中跟怪物追加的连打,一套连招打完之后,我对怪物相当于造成一个最终一击的状态的时候,这个时候其实对反馈更加要求难度大,同时要求更加真实。
《鬼泣-巅峰之战》的研发过程当中,Unity 的引擎解决了哪些研发中遇到了印象最深的问题?
缎君衡:我认为 Unity 在我们做《鬼泣-巅峰之战》帮助最大的就是动画状态机。我们之前尝试过自己做一些控制器和编译器,但是效果都不太好。我们在最后切到 3D 机以后,比较大的优势就是对我们的工作流非常友好,美术可以在没有任何逻辑代码的情况下,可以直接预览这些动作切换、动作融合,包括 BlendTree 这些东西都可以直接实时预案,不用运行游戏。对策划帮助也比较大,策划其实可以在这里做各种拓展。
对程序的好处一个是在这个动作融合的 BlendTree 上,《鬼泣-巅峰之战》用的 BlendTree 会比较多,牵扯到很多层,整体来讲效果比较好,另外一个就是,包括动作状态的分层,如果我们自己去做我们相对来说有一些难度,效果可能也没有它的这么好。觉得这块给我们的帮助是最大的。
SRP 底层渲染流程及原理
大家好,我是来自于 Unity 的高级解决方案工程师李中元,今天给大家带来的分享是《SRP 底层渲染流程及原理》。
我们先来看一下整个 SRP 的简单的架构,首先上面的这一层是我们常见的 URP 跟 HDRP,还有自己去扩展的一些自定义的渲染管线,一般这些上面的渲染管线会依赖于 SRP Core,SRP core 为我们提供了一些 Common 库,工具函数,还有 Shader Library,再往下是其实是我们 Unity C++ 层面的一些东西,包括我们给大家提供的 Context,Culling,还有各种 Draw 的函数,当然了还有 SRP 独有的 SRP Batcher。
这部分对大家来讲更多是一个黑盒,今天分享的目的就是为了帮大家把这个黑盒打开,通过理解 Unity 在底层做了什么事情,大家可以利用这些信息去更好的优化自己的项目。
我们先来看一下今天分享的内容,今天分享的内容包括三个大的部分,一个是 culling,就是 ScriptableRenderContext.Cull,Culling 就是我们在去调 Cull 函数的时候,Unity 底层会做的一些事情;还有我们的 Draw,Draw 其实包含我们调用的 CommandBuffer.Blit、CommandBuffer.DrawMesh 这些常见的用的 API,还包含 DrawRenderers、DrawShadows 这些 API 等等;最后是 Submit。
我们先来看 Culling,Culling 分两部分,一个是 Culling 的过程,还有我会跟大家引入两个概念,一个是 RenderNode,还有一个 RenderNode Queue,我会跟大家讲清楚,Unity 为什么会引入这两个概念。
我们先来看一个小 DEMO,左边是场景中灯光的设置,我们看到场景里面有四盏灯光,每个灯光其实都是实时的灯光,然后每一个光都会产生阴影。
我们每一个场景里面的每一个物体都会产生阴影。
通过我们运行这个 DEMO 查看 Profiler 的 Timeline 视图,能看到整个 Culling 的过程是这样的,看上去比较复杂,我们先一点点看。
首先我们来看这个红框框起来的部分,这部分是 Shadow 的 Culling。
我们先来尝试着把场景中四盏灯光中其中三盏灯光的产生 Shadow 的选项改成 NoShadows。
再回来看一下,这个时候我们就发现整个的 Shadow Culling 的 Job 从四个减到了一个。我们可以看出 Unity 底层会为我们每一个产生阴影的灯光创建一个 Shadow 的 Job,去裁减我们整个的 Shadow。
再来看一个小细节,下图箭头指向的标签——CullShadowCastersDirectionalDetnail。
这个跟什么有关系呢?我们继续修改我们场景里面的设置,我们把场景里面物体的会产生阴影的选项给关掉。
这个时候再运行 DEMO 看一下,这部分运行的开销也小了。
到这里我们能够发现,如果我们想去减少整个阴影部分的裁减开销,首先要去检查一下场景里的灯光是否有必要产生阴影,其次要去检查场景里的物体应不应该产生阴影,如果发现不合理的应该把这些选项都关掉,这样阴影裁减这部分的开销就能够降下来。
我们看完了阴影部分的裁减工作,我们再来看一下左边红框里面的部分,这部分是做什么的?
这部分其实是在裁减场景里面的动态物体,这几个 job 会产生一个 IndexList 这么一个数据结构,在 IndexList 存的是什么东西呢?Unity 内部维护了一个场景里面所有 renderer 的列表,IndexList 存的其实是当前可见的 renderer 的数组下标。我们通过一个例子来看一下这些 job 是怎么工作的。
在这个例子里面,上面的部分从 0 到 19,这些其实就是 Unity 内部维护的 endererlist,然后我们产生了四个 Job,分别处理 List 里面不同的部分。大家能够看到底下,底下就是我们 IndexList,对于 IndexList 这里能看到一个特点,就是这个 list 跟我们 Renderer 的 list 是等长的。
我们每一个 Job 处理裁减的时候,比如拿橘色 Job 作为例子,我们能看到在这个里面第四个是不可见的,这时候 Cull Job 就会把 0、1、2、3 写到 IndexList 里面去。我们再来看一下紫色的 Job 是怎么做的,7 和 8 中间这两个元素是不可见的,我们应该是把 569 三个 Index 写到 IndexList 里面去。但是,它并没有在 3 后面插入我们的 569,而是在自己的区间,比如说它只会访问我们的 5 号到 9 号的 IndexList,所以说第一个能访问的 IndexList 是 5。所以,它把 569 写到了这个位置,后面也是依此类推的。
这部分为什么 Unity 这么去做?
首先每一个 Job 都是在不同的线程执行的,如果说每一个 Job 去获取的数据,他们中间没有任何的交集,我是不需要引入锁的,所以他们访问的所有的数据都是线程安全的。
当我们写入 IndexList 的时候也是同样的。当我们写入的时候,橘色 Job 只会往 01234 这四个里面写数据,紫色往 56789 这里面写数据,我们在写入的时候也是不用考虑锁的,每个 Job 读写的数据都是独立的,所以我们的 job 里面是不需要给任何数据加锁的,这个就保障了我们 Culling 过程的速度。
但是这会带来一个小问题,就是我们产生的 IndexList 本身是不连续的,我们能看到 0123 和后面 569 中间会插入一个 0,9 后面有两个 0,这两个数据是不可用的。
Unity 后面会怎么做的?在 Culling Job 完成之后,会有一个 combine 的操作,会把不连续的 IndexList,就是把后面这些数据拷到前面的空格里面来,这样就会产生一个连续的 IndexList,我们这时候就得到了场景里面目前可见的 Renderer 数组的下标,这样就完成了场景里面动态物体裁减的过程。
以上只是 SRP 底层渲染流程及原理的部分内容。
Unity SRP 底层渲染的更多“黑箱秘密”,都在 B 站完整版视频中,点击即可前往。
关于云畅游戏
云畅游戏成立于2013年,总部位于北京。我们是一家「研运营」一体的游戏公司,多年深耕 IP 手游领域,并拥有 3D 动作、二次元等垂直品类开发经验。公司成功打造并推出了《不良人2》《航海王:燃烧意志》《鬼泣-巅峰之战》等多款正版授权手游,旗下在研项目还包括《不良人3》及 DC 授权的《蝙蝠侠》手游等。我们致力于成为高品质游戏的代言人,希望有朝一日能够跻身 3A 游戏殿堂。为此,云畅游戏仍在不断把研发实力推到新的高度。