12月2日,2021虚幻引擎技术开放日(Unreal Open Day ,以下简称UOD)在上海正式举办。在本次UOD的第二天,来自完美世界的游戏工程师李宁徽就《幻塔》的开发过程与我们分享了一些实现开放世界交互效果的心得。
他的分享聚焦于技术实现层面,主要围绕两部分展开,分别是玩家与地形,玩家与物件的交互,包括如何在算法层面实现自主攀爬、翻跃等。
为保证阅读体验,内容有所删减:
大家好,我是来自完美世界的游戏工程师,我叫李宁徽。今天给大家带来的是《幻塔》自由世界的制作分享,我将结合我们自身的开发经验,聊聊如何在开放世界中实现玩家与地形、玩家与物件之间的交互效果。
在这之前,请允许我简单介绍一下游戏。《幻塔》是一款废土轻科幻背景、开放世界的ARPG手游,其中包含丰富的大世界玩法及爽快的战斗体验,玩家能够在这款游戏中体验开放式的成长线和引力式的目标感。
在《幻塔》中,玩家可以自由探索世界,进行交互。比如遇到障碍时,可以自主选择跨越或攀爬,落入水中可以自主游泳。当然我们也对世界中的物件进行了交互处理,玩家甚至可以去游乐场坐过山车。这次的分享就会围绕制作过程展开,会聊一聊我们在开发这些有趣玩法中碰到的一些问题和解决方法。
可能分享的视角有些特别,尽管是开放世界主题,但并不是从游戏设计或游戏玩法的角度出发,而是从功能实现展开。
功能实现是什么呢?在游戏世界里,玩家走过的地面上会留下脚印,趟过的水面会泛起涟漪。玩家对大世界有所动作,那么大世界一定也会给我相应的反馈,这就是功能实现。我又将其分为三个类别,分别是地形探索、物件探索和玩法探索。
一、地形探索
首先是地形探索,能够看到玩家在地面奔跑会留下脚印,再进入水中,水会有所波动,再从水中来到地面,此时会产生新的脚印。
如果仔细看的话能够发现,从水里出来的脚印颜色更深。那么在《幻塔》曾经的版本里,这些从水面到陆地的脚印我们有做水渍效果,但后来我们把它去掉了。
那么我们为什么要去掉呢?因为在过去的不少游戏中,已经实现了这样的效果,并且做得很好。甚至在不同的地表比如沙子、草地、泥土上能够留下不同的脚印。再比如说在雪地上,通过一些贴画或者粒子都能实现凹凸不平的效果,这样的做出来的脚印非常真实。但是这样做会有一个很大的问题,就是消耗很大。
毕竟我们做的是一款手游,手机的内存是有限的,手机的温度、续航都是手游体验过程中非常重要的部分,这就要求我们做出取舍。
那么我们是怎么做的呢?我们利用简单面片创建一个脚印Actor,并且针对这个Actor做缓冲池处理,比如透明度、是否显示以及显示位置,通过对这些属性的控制,不再反复地创建和销毁,进而降低消耗。当然这也只是我们根据游戏优化的实际需求做出的取舍。
上面提到的脚印是玩家与地面的交互,而涟漪就是玩家与水面的交互。我们可以看到,当玩家从水面经过时,水面呈现波纹状态。但如果使用类似摩托车的载具经过时,波纹会呈现出另一种类似被推开的效果。当然游泳的过程中,比如入水特效、水花特效等等,都是玩家与水面交互的效果呈现。这些效果只呈现在水面上,并且产生的效果会因交互对象的形态产生变化。
除了地面、水面,玩家还会和空气产生交互,就是游戏中的能源系统。虽然都是火元素,在不同的区域中却能够带给玩家不一样的表现。比如在温度较低的区域,玩家会有搓手或者生火的表现,但是在温度较高的区域,玩家会有用手扇风的动作。其实想要实现这样的效果并不难,只需要在不同的稀有区域中添加触发器盒子即可。
在此基础上可以添加其他玩法,比如添加buffer,这本身实现难度不大,但却很能优化玩家的体验。
接下来我们谈一谈针对障碍物的跨越、翻越。移动组件里有一个属性叫MaxStepHeight,类似临界值的存在,当障碍物超过这个值时,玩家就会被挡住。这时候玩家要么沿着障碍滑动,要么被挡住无法前进。
虽然我们可以通过跳跃翻越障碍,但以手机为载体时,不停地按跳跃键,必然会影响限制其他操作,并且也不是所有障碍物都能跃过。因此在开放世界手游中,角色自动翻越的需求就十分强烈。
有不少传统方式能够实现自动跨越,但都各有局限。比如添加标记或者触发盒子,都能实现效果,但带来的工作量也很庞大。于是我们尝试寻找一种,对障碍不设限且给予美术自由发挥,并且能实现自动翻越的程序,下面是我们的答案。
我们所采用的方式完全由程序驱动,我们称之为美术策划驱动方式,也就是说,我们对地形对障碍没有添加任何限制,美术可以随意发挥随便做,我们也不添加标记、触发器,从头到尾完全是程序自发完成。程序会自主检测地形推算障碍高度,计算跨越障碍的落脚点,并根据高度自主选择合适动作进行跨越。
这里显示的三种跨越动作分别是低、中、高三个层级。低级的话,脚一抬就上去了;中级高度需要游戏角色做出手支撑的动作才能翻越;那层级再高时可能要手够一下,才能翻得过去。目前这项技术已经比较成熟了,只要能够拿到资源,程序就会自主分级并实现效果。
那我们是怎么做到的呢?移动组件中其实有自带这一效果的模式,但是这几种其实并不适用于跨越,那我们就创建了自定义移动模组以实现跨越。基于移动组件本身强大的可维护性、可重复性和可扩展性,我们省去了很多麻烦。
那么实现跨越效果的关键之处是什么呢?其实就是落脚点的计算,一旦落脚点可以确定,那么跨越高度也就明确了,此时就可以选择合适的动作完成跨越。
在计算落脚点时也有一些细节要注意,比如地形。如果是个斜坡,那么它能够让人站上去吗?同时我们还需要考虑玩家的站立半径以及动作的衔接。除了陆地上的跨越,还有不同地形间的跨越。比如在水中如何跨越障碍,这时候的落脚点计算与在陆地进行计算是不一样的。
当然有的障碍甚至超越了我们设定的最高障碍,此时只能攀爬而不是翻越。这时候我们怎么处理?其实很有趣,因为正好和之前处理翻越的方式反过来,那些传统的打标机,开发触发器等方式在这时候就很适用。
在这些环节也有一些比较重要的点,主要是地形。因为在这个环节美术不受限制,因此地形会比较多变。那有的稀奇古怪的地形,就会给我们的落脚点计算带来困扰。
举几个例子,无论是倾斜的、有凹槽的还是浮空的地形,有一个节点,胶囊体底部不是在地表的情况,导致计算障碍高度不准确的情况,这就需要在检测算法中添加额外检测来确认节点以避免问题。所以其实我们的算法也是个不断优化、成熟的过程。
上面提到我们的攀爬是由程序自主驱动的,因为攀爬本身是脱离重力的表现,一旦玩家脱离攀爬状态,就会自然坠落。
针对这种情况,我们利用移动组件中的F模式模拟攀爬状态,并建立自定义子模式对应不同情况,比如各个方向的自由攀爬、攀爬跳跃、攀爬回跳、雨天攀爬打滑的状态以及攀爬到边缘翻上去的状态,都由自定义模式处理。通过一些动作效果和蒙太奇润色,这个攀爬的状态会更自然、合理。
这里我们以三种攀爬情况为例,分别是行走进入攀爬,下翻进行攀爬以及空中进行攀爬,还有一种从水中进入攀爬。其实它们的攀爬计算方式是都不一样的,但是有一个共同点,就是我们要寻找可攀爬的面。
在我们曾经的版本中,有带给过玩家非常不好的游戏体验。比如说一个房间,玩家进入可能就是想到处看看,但是这个系统无法判断玩家的意图。当玩家走到柜子前面,可能就会自动爬到柜子上面。那么我们是如何解决这一问题的呢?
我们采用了两种方式,一种是在游戏设置里加入选项,那么玩家就可以自主选择是否自动进入攀爬。此外,我们添加了进入攀爬的动作,在动作中添加AnimNotifyState,如果期间没有移动输入没有打断,就进入攀爬,如果停掉了,就表示不想进入攀爬,那么攀爬动作也会停止。
很多的游戏有很多攀爬的功能,还有关于攀爬的教程。在这部分教程中,攀爬动作由5个位移构成。我们采取了不太一样的方式,我们所有的攀爬动作蒙太奇都是不带骨骼位移的,包括位置、速度、朝向都是算法自动生成的。这样就能够避免动作在实现过程中受到位移和朝向的影响。
一般的运动模式,比如行走、游泳都是在X轴和Y轴这类水平方向上的,但攀爬是3个维度。这就很巧妙,为什么巧,因为攀爬与地形息息相关,攀爬的动作要求角色紧贴地形。
所以在X轴,也就是前后方向,我们始终让角色前行并且贴着地形,这样我们就可以把我们更多的注意力集中Y轴和Z轴方向,也就是左右方向。这相当于把三个方向的计算简化为两个方向的计算。
这里大家可以看一下,特别是在转弯的时候,需要格外注意玩家的朝向,特别是凹地形。因为角色的手是沿着地形划过来的,这是用IK实现的。
接下来是攀爬跳跃,这其实也是攀爬中的重要元素。以玩家键盘或者遥感的输入方向作为攀爬跳跃的方向,并按照这个朝向做出检测,事先我们会把攀爬跳跃的速度距离等要素作为参数盘出来,由策划进行配置,然后根据当时的地形,检测计算出攀爬跳跃后的结束位置。
玩家的摇杆或者是键盘输入会提示方向,我们需要沿着方向做事先检测,计算攀爬跳跃的结束点,然后配合蒙太奇完成跳跃。左边是各个方向的攀爬跳跃,然后中间是凹凸地形,然后右边是攀爬到顶的翻越动作。
大家可以看到,如果站在台阶上,我的脚分别在两个不同的台阶上。当地面不平时,脚面要贴着地面做旋转。那么当攀爬时,我的手要抱着树,如果是凹型地面,手则分别搭在两边。
现在有很多关于IK(Inverse Kinematics,反向动力学)的好用的教程或者是插件,比如说Power IK。但它对于移动端的支持还有一些可优化空间,特别是在旋转方面。所以我们游戏中角色的IK基本上都是我们自己实现的。
接下来是滑板,它是所有地形探索里唯一一个涉及到海陆空三模式的交互方式。也就是说我们需要在移动组件中Walking、Falling、Swimming三种模式来实现。
这我们就需要注意模式之间的切换,我们要保证动作和速度可以平滑过渡。我们这样做也是为了让玩家在操作时能有爽快的感觉。
下一个是滑翔,因为人类是不能够飞的,所以在游戏里实现飞行会是很新鲜的体验。它是通过重组移动组建的Falling来做的,我们优化了转向、重力加速度等等来实现效果。
然后是游泳,我们有添加一些自己的内容,比如加速游泳等等。针对这些我们做了转向上的优化,因为手机摇杆有些特别,为了优化操作手感我们也做了不少努力。
说到这边我们就说一下我们地形探索遵循的原则,就是胶囊体负责位置,Mesh负责表现。
胶囊体负责碰撞以及位置同步,且所有地形检测算法都是从胶囊体开始的,就是说胶囊体是个出发点,我们所有的地形检测算法都是从它开始,然后做检测的。而Mesh负责视觉效果,比如视觉效果的平滑过渡,比如说不同状态之间的切换,比如说动作的衔接,包括IK。
我举个例子,比如说我攀爬的时候,好巧不巧,前面正好有物体把角色顶出来了,那么胶囊体的位置就会发生偏差。但此时的Mesh是不能歪的,一旦歪掉角色就会掉下来,脱离攀爬状态。
这时候我们可以让Mesh往前抓,去紧贴地形,这时候相当于我们允许Mesh略微脱离胶囊体,从而避免偏差。
二、物件探索
我们再看物件探索。首先是推箱子,我们实现了两种推箱子,分别是模拟物理的和不模拟物理的,它们各自的优缺点。
我们先说模拟物理的,这一形式的推箱子在掉落、碰撞、翻滚时表现特别自然,效果特别好,但它有一个缺点,就是不可控因素太多。当地形不平整时,碰撞的角度不一样,或者碰撞速度、碰撞力度不一致时,其呈现效果是完全不一样的。
特别是与玩法相关时,类似左边的机关。它要求玩家把我这个箱子推到指定位置才能打开机关,如果使用模拟物理的箱子,可能每次结果都不一样,那么就无法完成任务,所以我们就做了第二种不模拟物理的推箱子。这时候无论是推10次、20次甚至100次,玩家始终能把箱子推到固定点,并且结果一致。
在大世界中,我们交互的箱子一般采取模拟物理的形式,而在机关或者一些简易玩法里,我们采取的是这种不模拟物理的形式。
另外我们是这样实现举抛的。通过制作ControllableComponent的组件,凡是拥有资格组件的物件都可以进行操作,其中包括抛举。抛举实质就是将物件放置在固定点上,配合半身动作实现移动效果。
在抛举过程中,我们会在玩家输入的初始方向上叠加初始速度,然后模拟物理,最终实现效果。
接下来是砍树,这是由Foliage植被系统刷出来的,当角色在挥刀砍树时,刀的Mesh会与树的Mesh发生碰撞,此时树会呈现被砍到的效果。后续我们会用新的树的Mesh替换被砍掉的树桩的Mesh,完成资源更新。
然后是砍草,这实际也是由Foliage刷出来的,但草和树不同,因为草与刀之间没有碰撞,所以我们选择在AT表上做标记,直播砍草呈现效果。在一段时间后去除AT标记,然后把草重新刷出来。
三、玩法探索
关于物件的探索我们就说到这里,现在来看看过山车。这个环节中我们提供了多种视角,玩家可以根据自己的喜好做选择。
起初我们并没有计划做过山车,那为什么会做这个呢?因为之前有玩家说,“我们都到了游乐场了,结果只能看到这里有个过山车,却玩不了,我们想玩过山车。”
那好,我们就做了这个过山车,当然也还有旋转木马。其实游乐场里这些摩天轮也好,过山车也好,从技术层面想要实现是并不难的,因为这些娱乐设施始终沿固定轨迹重复运行,所以原理比较简单,但却能够给到玩家身临其境的游戏体验。
这对于部分用户而言是很有“杀伤力”的,他们不仅愿意和朋友一起玩,甚至会主动宣传,带动身边的朋友一起体验,进而丰富游戏中的交互体验。
在技术层面我们需要提供多种不同的视角供玩家选择,也需要布置Spline,保证过山车的移动速递和节奏,还原真实的体验。
我们在游戏中还做了更多的交互设置,比如我们做了抓手,扔一个东西过去,然后一下拉回来,又比如磁场等等,还有一些基于既有玩法和物件的交互,比如踢足球、保龄球、抓娃娃等等,目前我们已经实现了游乐场内所有道具的交互。
本文转载自手游那点事,侵权请联系删除