这个mini项目挺屌的

小明 2014-12-2 5,928 views

【Paradise Lost】追求质量之路(杭19)

 人们说它追求的是逼格,其实它追求的是质量。或者换句话说,质量即逼格。本文分享了《Paradise Lost》开发过程中程序对产品质量进行的一些努力。 

引言

转眼间Mini项目已经过去了一个多月,和小伙伴们一起奋战的日日夜夜还历历在目。大家日日夜夜的汗水最后凝结为《Paradise Lost》这款解迷手游。在我们的游戏里,玩家可以在两个相似,而又略有不同的世界中进行切换。通过切换世界,可以分别操作一男一女Aaron和Liz两个角色。其中,在Liz的世界中,Liz所有的动作都会被记录,当切换至Aaron的世界时,Liz的动作则会进行重放,玩家需要合理而巧妙的利用这一段时间的重放,操作Aaron与自己之前的操作的Liz相互配合,从而协作解开复杂的谜题,解开整个故事背后的真相。

 

 

整个游戏采用剪影方式表现,配合多层滚动背景实现纵深的立体感,以下是这款游戏的运行画面:

人们说它追求的是逼格,其实它追求的是质量。或者换句话说,质量即逼格。

为什么说质量是至关重要的呢?对于一款解迷类游戏来讲,在玩家在刚刚拿到一款游戏时,对它产生的第一印象就来源于此。在尝试去花时间熟悉游戏的解迷规则前,欠佳的游戏质量往往会造成玩家的直接流失。于是在项目开始之初,我们就将质量定位为一个十分重要的核心目标。

(警告:前方大量废话预警)

于是问题来了:质量是什么?

于我来说,质量是统一的整体,丰富的细节。

游戏是一种特殊的艺术形式,优秀的游戏会传递给玩家一种整体的体验,这种整体感使得玩家可以无缝的投入到游戏构架的世界中。即便这个世界只有挂着糖的绳子和一只青蛙,只有一堆五颜六色的宝石,甚至只有一只上下飞的小鸟,玩家都觉得它很合理,”就应该是这个样子”。整体感归根结底来源于游戏内在各种元素千丝万缕的相互联系,例如在这样的画面风格下,人物的动作风格应该是怎样的,是欢快还是平静?音乐的风格应该是怎样的,是沉重还是悠扬?在整个游戏的制作过程中,我们的策划,美术,程序反复探讨着这些问题,为项目理清大的方向。

单单讲整体,什么都没有不就很有整体感吗。质量同样意味着丰富的细节。优秀的细节是隐于无形的,它让玩家产生了某种体验,但玩家却没有注意到它在这里。举一个例子,在我们的游戏中,地面上的草是一个场景表现的细节。当玩家走过一片草丛时,草会随之自然的晃动,这一点对于玩家看来是理所应当的,它潜移默化的加强了玩家在这个场景中的代入感。而反之,假如一片草丛是始终纹丝不动的,这片草丛的人工感就会变得比较强,反而更容易引起玩家的注意,变得非常刺眼。此时,这个草丛就成为了一个不好的细节。

最后,对整体感和细节性的追求成了我们整个项目组的一种强迫症。比如经常会有这样的话:“我觉得背景的云彩有点不和谐”,“我觉得灯光下应该有小飞虫”,“这个起跳的动作有点僵硬”。每个职能的小伙伴都在尝试着把它当作一个艺术品来做,做成我们心中想的那个样子。

作为程序在关注什么?

我们作为程序,是从另一个侧面关注这个问题的。什么?你给我说策划的单?策划的单其实仅仅是一个地图,告诉你旅途的终点,而向哪里走怎么走就是程序自己的问题了。

问题之向哪里走

向哪里走就是程序的实现的技术路线了,究竟是抄了近道去的,还是为了到目的地先修了条马路?自然是各有各的好处。程序实现的是这个架空世界的逻辑,承载了之上的表现,也就是我们所说的整体感和细节。在程序这一层面,一曰稳定,二曰性能。没有此二点,再好的整体感和再丰富的细节都是空谈,抵不过玩家觉得太卡经常闪退怒删游戏。

问题之怎么走

同样,同一条路,我们究竟是开车呢还是走路呢?怎么走的问题,提醒着程序们要时时刻刻注意为开发流程负上责任。我们是不是可以给美术提供更直观的工具来所见即所得的调整画面效果?是不是应该在美术资源的集成上想一些快速的方案?是不是可以让策划不需要填表,拖放一下点一点就可以搭建好关卡逻辑?

所有这些的背后,都是为高效的实现一个高质量的产品而努力的过程。

本文后面都有啥?

下面想从一个程序员的角度,来分享开发过程中遇到的几个有意思的问题,这些问题都非常的直白,但无一例外的,都直接关系着游戏的表现和体验。解决方法也许简单粗暴,但我更想向大家展示的是整个解决问题的思考尝试过程,姑且算是对前一段的一个自我总结。当然,如果碰巧能帮上谁,那就再好不过了。

那么话题呢,主要有这么四个:

  1. Bloom特效的性能优化
  2. 一种简单的迭代IK求解算法
  3. 触屏输入的处理
  4. 高效的植被物理模拟

 


Bloom特效的性能优化

为了使得游戏场景中的光效更加和谐统一,我们希望画面中的元素能够有一些相互渗透,而不是生硬的止步于每个Sprite的边缘。经过一番考虑,我们选择了使用Bloom这一特效。这个效果通过抽取并扩散画面中较亮的部分,模仿人眼观察明亮物体时高光溢出到暗部的效果,使得场景中明暗交界的地方更加的自然:以下是关闭和开启该效果时的画面对比:

关闭:

打开:

明显可以看到,在打开Bloom后,整个画面显得更加的柔和自然了。

程序员都不喜欢重新造轮子,首先,我们在游戏中尝试使用了Unity3D引擎中自带的Bloom shader,在手机上测试的过程中我们发现,单单这一个特效就会导致整个游戏的帧率下降一半,引起明显的跳帧。后来我们发现,因为像Bloom这样的全屏后处理特效的性能开销较大,在移动平台的游戏中其实很少会使用。

此时的确是可以抛给策划和美术一句:”这个做不到“,但由于这一特效实在对于我们的游戏的画面实在有着决定性的影响,我们决定尝试着手优化这一特效的性能。

优化过程

首先简单介绍一下Bloom的实现机理,基本的Bloom特效的实现通常可以大致分为四个Pass:

  1. 提取高光部分
  2. 水平模糊
  3. 垂直模糊
  4. 将模糊的高光部分叠加回原图

性能问题主要出现在第2,3步:原Bloom shader中,每个模糊Pass中要进行7次纹理采样并进行加权平均,两个Pass共同产生一个大小为7的高斯模糊效果,这些计算占用了大量的硬件资源。于是我们有针对性地采用了三种方式结合,大大地降低了Bloom的开销:

降低Bloom计算Render Target的分辨率

少计算一些像素是最直接的想法。在提取高光部分时,我们同时降采样到1/8分辨率,这样,计算Bloom模糊效果仅需要对较少的像素进行。由于模糊的高光在图像上属于低频信息,降采样计算并不会造成明显的视觉差异。

将Pixel Shader中的计算尽量移动到固定管线硬件中

7次采样再加权平均实在是开销比较大,首先,对于两个相邻的采样像素,我们利用GPU sampler做采样时线形滤波器的插值能力,按照其权值的比例,仅在两个像素间的特定位置进行一次采样,这样子7次采样就变为了4次,采样位置如下图箭头所示。相较于原来的实现,这种方法相当于把一部分运算由Shader转交给了GPU固定功能硬件,得以获得效率的提升。

除此之外,原先shader中,采样点的坐标是在Pixel Shader中对纹理坐标加偏移量进行计算的,由于这些采样点的坐标都是均匀变化的,我们将这部分计算移动到Vertex Shader中,并利用Texcoord0~Texcoord3四个寄存器将每个Pass需要采样的四个坐标传入Pixel Shader中,GPU会对这四个纹理坐标进行插值计算出最终需要的采样坐标。这样,Pixel Shader中的计算就得到了进一步的简化。

隔帧计算特效

Bloom的模糊高光,不仅在空间上,而且在时间上也是一个低频信息,我们通过降低模糊计算的频率,进一步降低了其运行开销。具体实现上,我们保存了偶数帧前三个Pass的Render Target,并在奇数帧跳过前三个Pass,直接利用之前保存的模糊高光进行第四个Pass的计算,于是对于模糊的计算量就又减少了一半。

采用了以上三种方法后,我们将Bloom特效的开销降低到了一个可以接受的水平,换用新版的特效后,大家明显感觉到游戏变得比一开始流畅多了,但在视觉效果上则没有什么明显的变化。

美术同学终于松了一口气,“可以继续去加粒子了~”。


一种简单的迭代IK求解算法

在我们的游戏中,玩家可行走的地面并不平坦,应该说是相当崎岖的。然而人物的行走,跑动,跳跃的动画却都是固定的。为了使得人物在走动的过程中脚不至于陷入地面以下,根据地形有所变化,我们需要根据当前地面的状况摆放脚的位置,并反推出腿部关节的相关角度。

另外,为了便于玩家在复杂的场景中更加容易的攀爬移动,我们设计了一个攀爬边沿的动作,场景中被放置了一些可攀爬点,在人物跳跃接近场景中的可攀爬点的时候,会伸手抓住攀爬点,直到整个攀爬动作完成再松手,这也要求可以根据手的位置去反推相关关节的角度。

这些细节,都发生在角色和场景外形发生交互的瞬间,可以很好的让角色显得更加融于场景,真的在场景里奔跑攀爬,而不是漂浮在其上。这样些一需要根据一定条件反推关节角度的需求,都需要IK(反向运动学)求解器的支持。

我们使用的Unity3D引擎就内建了骨骼IK求解的功能,不巧的是,Unity3D引擎对2D骨骼并没有很好的原生支持,以至于我们游戏中的所有人物动作的动画资源全部是使用场景树/变换的形式制作,例如角色的小臂在场景树上是大臂的子节点,而小臂相对于大臂的运动由两者之间的空间变换的变化所定义。这使得我们不能直接使用现成的IK算法,而需要自行开发IK解算器。

关节定义

我们的动画资源只定义了子空间的变换关系,我们需要首先从中提取出关节的定义,从而进一步支持IK的计算。在制作动画前,我们和美术小伙伴约定,动画中的子空间仅可以围绕其原点位置进行旋转,这样,我们就可以方便地从场景树中重建出骨骼的结构,如下图所示:

IK求解算法

有了骨骼的定义,我们设计了一种简易的迭代算法来进行IK的近似解算,该方法可概述如下:

1.转动第n级关节,使得IK节点最接近目标。

2.转动第n-1级关节,使得IK节点最接近目标。

n.转动第1级关节,使得IK节点最接近目标。

n+1.当足够接近时,停止算法,否则回到第1步。

由于在这个方法的每一步中,都能保证IK节点比上一步更加接近目标点,从而可以在不断迭代的过程中,逐渐逼近IK的一个解。经过实验我们发现,仅需要大约4~5次迭代运算,就可以求得一个足够精确的多级关节IK解。如下图,展示了两节骨骼的两次迭代运算的过程:

这个算法十分易于实现,也可以较为直接的推广到三维的情形。当遇到没有合适的现成IK解算器可用的时候,也不失为一种不错的方法。

在此基础上,我们分别实现了足部和手部的IK,使得玩家在地面走动时,脚始终可以实在的踩在地面上,而在攀爬中,手也可以始终抓住攀爬点。IK的应用,还同时节约了美术制作起跳和落地相关动画的时间,仅仅需要调整玩家的重心,足部IK就可以生成自然的起跳落地时屈膝的动作。

关节约束

不得不提的是,人的身体的所有关节都有一定的约束,并不能完全的自由旋转。如果对IK的解算不加任何约束,最终计算的结果可能会是一个正常人做不出的怪异姿势(例如膝盖向前弯曲)。为了保证游戏中人物动画的合理性,我们同时在以上算法的基础上实现了关节旋转的约束,并提供了可视化的编辑工具以定义约束。

      

如图展示的是膝关节定义的旋转约束:

我们采用了一种非常简单粗暴的方式来实现这个约束,就是在每次更新关节角度的时候进行检查,当发现约束不满足的时候,则将骨骼旋转至满足约束条件的最近一点。这样做虽然有可能会陷入局部最优,使得迭代计算过早的稳定在一个次优解上,但是在实际游戏的应用中我们并没有发现这种现象,效果是令人满意的。


触屏输入的处理和控制判断

在游戏设计的初期,我们曾对游戏的交互设计纠结过很长一段时间,对于一款解迷类游戏,怎样才是合理的操作模式?

为了游戏的整体体验,我们首先放弃了虚拟摇杆的操作方式,对于我们的游戏风格来说,在界面上添加任何明显的操作元件都会干扰到游戏的美术表现。接下来有两种操作方式摆在了我们面前:

比较早期的版本中,我们采用的是点按方向移动的控制方式。这种交互对于玩家来说较为容易上手,但当玩家需要反复控制角色左右移动的时候问题就出现了:玩家不得不时而按住角色的左边,时而按住角色的右边,手指的可能触摸区域几乎是整个屏幕,需要玩家需要多次将手指离开屏幕,在较远的地方点击,对于手机游戏来讲,这样很容易造成玩家的疲劳。

另外一种操作方式则完全以滑动为基础,此时所有的操作都是以点按并略为移动手指来完成,我们在这里借鉴并且进一步推广了移动版Limbo的交互模式。此时,玩家仅需要在屏幕任何一个位置按下并小幅滑动即可完成操作,并且玩家在进行一连串操作时不需要中途放开手指。如下面的例子所示,当玩家想要先跳跃,再向左移动,再爬一个梯子时,点击操作方式需要三次点按屏幕的不同区域,而对应的滑动操作之需要玩家一次按下屏幕,三次轻移手指即可,极大地简化了操作。

当然,这样的操作方式也不是完全没有问题,由于硬件本身的不精确性,触屏输入的坐标总是伴随着一定的噪声。如果不进行妥善的处理,这些输入噪声的抖动会造成玩家的各种误操作,极大地影响游戏的体验。

一般的滑动操作的游戏多会设置一个操作的死区(Dead Zone),不响应位移小于死区的操作。我们的游戏由于要支持不松开手指的连续操作,所以并不能直接采用这种方式。另外,如何确认玩家的动机也是一个问题,存在这样一种情况,玩家较长时间的把手指按在屏幕上思考下一步的动作,无意识的将手指缓慢挪动了一段距离,此时基于死区的检测方式就会判断玩家想要进行移动,从而造成了误操作。

我们的实现

一个合理的方式是检测玩家手指运动的趋势,如果在过去较短的一段时间内,玩家的手指移动了一个较大的距离,我们就认为玩家有进行这个操作的动机而不是无意识的,我们为这种控制方式设计了一个简单的实现方法。

当玩家开始触摸时,在每一帧记录当前手指坐标和时间戳,存入操作等待队列,检查并移除队尾所有距当前超过一定时间Δt的旧节点。取最后一个被移除的节点,判断其位置和当前手指位置,如果距离超过一个值Δd,则认为玩家进行了操作,设置输入的状态。直到玩家进行下一个操作或移开手指前,输入都将一直保持同一个状态。当玩家移开手指时,该队列被清空。这样,只有当玩家的手指在Δt的时间内移动了超过Δd的距离,才会被判断为是进行了操作,最大限度的消除了输入噪声的影响,同时兼顾了连续输入操作的需求。

当然,这只是我们为了游戏的控制系统所做的努力的一个很小的局部,在开发的最后一个星期里,除了修之前的bug,一直在做的就是微调游戏的操作手感。游戏的控制状态机部分是整个开发过程中重构次数最多的一个模块,我们力求做到的一点是游戏的控制逻辑不对场景的设计做任何的假设,从而保证玩家在各种角落进行各种怪异的操作都不会陷入无法预期的状态。


高效的植被物理模拟

我们游戏的设计里,几乎所有的场景都位于野外或者城市废墟中,这些场景的一个共同特点就是有着大量的植被覆盖。杂草,灌木,以及乔木的存在,都为整个场景增添了丰富的细节,很好的营造出了破败的氛围。一开始,植被是静态的绘制在场景的背景图中的,体验后我们觉得这样子的植被有一种生硬的纸片感。我们的游戏采用了剪影的美术风格,静态的植物剪影容易让人产生剪纸的联想,从而产生一种人工感。

动态植物的几种实现

为了进一步提升我们的场景表现力,我们尝试着制作动态的植物。动态植物的本质在于,真实植物的枝条都不是刚体,而是会随风弯曲摇动的。首先,我们将原先整体的树木拆分成树干,树枝等更小的Sprite,再由根到稍拼合成一个多节的树状结构。这样,植物在每个关节都可以以不同的角度弯曲。虽然本质上仍然是一个多节的结构,但由于每个关节的弯曲都比较小,在实际观感上是比较难以察觉到这些关节的存在的。

接下来问题就是如何让这些关节动起来了,我们尝试了几种不同的路线:

美术制作动画资源

人为的模仿真实的柔性物体运动是较为困难的,如果制作的略有不自然,很容易让人产生一种感觉,觉得这个树是在自己主动运动而并不是在被动的随风飘动,而动画的周期性动作的特点又加剧了这种感觉。我们的游戏中要用到多种不同的树木,让美术同学反复迭代这些树木的动画显然从时间上是不允许的,所以我们决定动态的计算树木的运动。

Unity3D内建物理系统

新版的Unity3D已经内建了比较齐备的2D物理系统,我们同样可以用Unity内建的刚体/关节体系,让内建物理引擎驱动这棵树。但是这样做的直接结果就是,需要为组成植物的每个Sprite添加刚体组件和关节组件,并与父节点挂接。由于场景中的植物较多,实验显示这在性能上是不能接受的。

简化的植物物理模拟

Unity3D内建的物理引擎是对物理现象的一个较准确的模拟,对于树木摇动这样的情形是不必要的。通过建立一个更加简洁的模型,我们可以在视觉上保持自然的同时,确保合理的性能开销。我们通过对现有的树结构的一些力学上的分析,建立了一种可以更简单的模仿植物摇动的算法。

植物物理模型的建立

考虑到整棵树被简化成了多节的树状结构,我们这里仅需要针对植物的关节进行分析就可以了。在这个简化模型中,我们仅使用浅显的物理知识对其进行定性的近似计算,故而极大的减少了最终需要的计算量。

此时我们以一个旋转关节模仿一小段弹性树枝,并进行一些符号约定,如图所示:

在这个简化模型中,我们仅进行定性的近似计算。此时我们以一个旋转关节模仿一段弹性树枝,如图,考虑树枝发生了角度为θ的扭转,那么可以计算出回复力矩M,有

此时假设关节下部固定,上半部分以关节为轴的转动惯量是J,则该关节获得的角加速度为

即可以近似的认为,一段树枝获得的回弹角加速度与其偏转的角度成正比C,当这个比值越大,代表同样的弯折下,枝条会更快的回弹。日常经验告诉我们,树木越靠近末端回弹的要越轻巧快速一些,所以我们给树枝末端赋予经验性的更大的常量C。事实上,这是因为在考虑靠近树根部位的弯折时,回弹的并不仅仅是上方这一节树干而是之上的整棵树,相对于转动刚度k的增长,转动惯量J在树根部增加的更快,造成了较小的C的值。

这样,我们把一棵有弹性的树的计算简化为了在每个关节处的局部的局部计算,整个计算的时间复杂度仅为O(n)(其中n为关节数量)。

整个算法的过程如下,在每一帧,对于每一个关节做如下计算:

经过这些简单的计算,就可以得到下一帧中所有的关节应该具有的角度。为了进一步缩减需要的计算量,我们仅对屏幕可见的植物进行计算,进一步降低了植被摆动的性能开销。在实际的游戏的运行中,这一算法展现的效果是十分自然的,而且仅占用每帧很少的CPU时间。

而在使用上,仅仅需要给植物的根节点附加这一脚本,并调整相应的参数即可。脚本会自动遍历整棵树的结构并在每一帧进行相应的物理模拟。

向植物施加力

为植物建立了物理模型,下一个问题就是如何让给这些植物施加力使其运动起来,对于实际的树木,即便受到的风完全是理想而稳定的,树木依然会以一种随机的方式运动,而不是最终收敛稳定在一个姿态上,这是因为每一个树枝都在影响着吹向其他树枝的气流的方向和强度,而因树枝复杂的结构造成的湍流更加剧了这种随机性。

为了模仿这种随机性造成的结果,我们采取了一种非常简单的方式,即以随机的时间间隔给每一个关节施加对应风向的一个随机力矩,这样效果在最终的游戏中已经比较让人满意了。如果想要追求更好的效果,可以持续的给关节施加一个Perlin噪声扭矩,这样植物的摆动会更加的连续而流畅。

对于草的运动,除了受到风的影响之外,程序会给每一丛草添加一个合适的碰撞体,当检测到与玩家角色的碰撞时,则朝向玩家的运动方向给草丛施加一个额外的随机的力矩,这样,当玩家走过一片草地的时候,就可以观察到草被自己带动而摇摆,进一步的加强了场景的带入感。


写在后面

游戏的最可贵之处,在于它扩展了人类的眼界,让每个人都得以体会那些在现实生活中不可能见到的奇异的景观和动人的故事,使人们可以籍由他人的想象力起飞。最好的游戏,首先是一个艺术品,其次才是一个产品。作为这之中的程序,我们在努力的打磨着这个虚拟的世界,让每个人的创造得以最好的凝结。

特别喜欢游戏这个行业的原因在于,它前所未有地把几种最具创造力的人聚集在了一起,一起去进行一种探究和开拓。这样的思维的摩擦和碰撞,无疑是激动人心,而又让人受益匪浅的。一起和小伙伴们开发mini项目的一个多月,虽然非常的疲惫,但同时也无比充实。

    已经搭了5 块砖头了!

    1. 梧桐梧桐

      听起来挺有意思的游戏,在哪里可以玩?

      • 小明小明

        貌似没有上线的机会了,是个内部游戏

    2. vegeancevegeance

      内容很受用,并且不失趣味。
      –游戏的最可贵之处,在于它扩展了人类的眼界,让每个人都得以体会那些在现实生活中不可能见到的奇异的景观和动人的故事,使人们可以籍由他人的想象力起飞。 😆

    3. vegeancevegeance

      唯一的遗憾是 我发现这边文章中的图 我一张都看不到 ❓

      • 小明小明

        从内部论坛粘过来的,原来外网是看不到图片的。。。


    欢迎拍砖!