在 2D 横向卷轴动作类或冒险类游戏中,其实“楼梯”并没有想像中的常见,特别是强调平台跳跃的 2D 游戏中通常完全不会出现楼梯。其中一个重要原因,就是楼梯相关的人物动态实作起来并没有想像中的简单;但万一游戏规划上或场景的合理性上需要大量使用楼梯系统呢?德国独立游戏团队 DigiTales 的 Julian 前阵子分享了一篇这样的文章,我们取得翻译授权整理如下,供有需要的开发者参考。
前提
上下楼梯功能长久以来对我来说犹如芒刺在背。我们在 2017 下半年时首度把这功能实作进当时的原型里,而这些程式码直到前一阵子都没有什么改变。就算以功能原型的角度来看,那个实作仅勉强堪用,当然也不该保留到游戏上市版本之中。
不过呢,正因为它造成许多程式臭虫并凸显出了各种容易踩到的坑,我现在可以跟各位谈谈在设计自己的系统时,该注意哪些地方。我说“设计”是因为我会着重在游戏设计的部份,关于程式怎么写只会大概提一下概念,因此就不把实际的程式码拿出来讲了。不然这篇本来就已经够长的文章包准会一发不可收拾。
首先我要先快速说明我们移动系统的重点功能。如果你的需求完全不一样,那接下来的解决方案可能对你不太适用。
- 游戏本身是人物可以行走和冲刺的 2D 横向卷轴,不能跳跃和奔跑,也就是说人物只能从底端和顶端两处走进楼梯。
- 在楼梯移动时有专属的动画,不沿用平时的水平行走和冲刺。
- 楼梯有两种:正向梯(直立)和侧向梯(对角方向)。后者可再分为“左上至右下”与“左下至右上”两种方向。
- 楼梯每一阶的大小相同,以配合各种玩家角色动画。楼梯长度不拘。
- 人物模组的碰撞区(与楼梯动作有关)在大腿附近。不过从概念上来说,这并不是什么必要的条件。
- 没有战斗和其他因素会对在楼梯上移动的人物施加外力。
- 设计目标是为了做出直观、不易出错又赏心悦目的楼梯动作。
了解所有前提之后,我们这就来开始叠代,把冒出来的问题一个个解决掉吧!首先从侧向梯(对角方向)开始。各位之后会发现我们即将设下的原则大多也适用于正向梯(直立)。
第一步:走上楼梯
首要之务就是让人物登上楼梯。我们先做出非常基本的第一代楼梯:当玩家即将通过楼梯时,会先触发一个碰撞区,将人物切换为楼梯上的操控方式(我们称之为“穿过检查点”,穿过一词与后面需要的功能相关,详后述)。楼梯的另一端则有另一个检查点切换回一般操控。
第一个要解决的问题乍看之下可能觉得很难发生,但实际上相当重要,需要及早考虑:人物碰撞区可能不会在正确的时机触发检查点,尤其在低影格率、而玩家移动速度又和影格率脱钩的时候(当然,游戏逻辑与影格率理应脱钩)。当影格率比较低的时候,每张画面之间的移动距离就会变大,而这个距离越大,玩家人物就越容易冲过检查点。
在影格率高的情况下,人物会逐渐接近并在适当距离触发检查点:
然而在低影格率时,可能会造成人物碰撞区的尾巴去触发检查点:
这会导致行走动画和楼梯图像不同调。因此我们在第二代楼梯中,必须要从接下来的两种解法中择一实作:
- 单纯地在开始或结束楼梯上移动的瞬间,将玩家人物传送到正确的“进入点”或“离开点”位置。因为传送的距离只会在低影格率的情况下才比较大,当游戏已经执行不太顺畅的时候,这个传送效果也不会太突兀。
- 让物理运算与画面绘制脱钩,这样自然就不会遇到影格率问题,因为碰撞的逻辑与运算与画面更新切开了。如果你使用 Unity,FixedUpdate() 就可以处理这个问题,它每秒都会执行固定的次数,而不受影格率影响(预设为每秒 50 次,这个值可以调整)。
不论你使用两者中哪个方法(第二个比较推荐),在本文中会保持使用“进入点”与“离开点”这两个用词,毕竟不论方法为何,它们在概念上仍然是相通的。
现在让我们来讨论下一个比较明显的问题。要是玩家想要能穿过楼梯呢?我们的设计到目前为止,人物一走到碰撞区就会走进楼梯。
第三代解决这个问题的做法,是在楼梯入口定义一个不同的碰撞区(称为“进入范围”)。只有在楼梯底端按住方向键上或在顶端按住方向键下,人物才会开始上下楼梯。左右移动只会让人物穿过进入点,继续一般的水平移动。
现在问题变成了一旦玩家在进入范围内按住上或下,人物就会在当下所在位置直接开始进行上下楼梯的动作,而非从确切的进入点开始。就算他们先传送到进入点(我们在第二代中做了这个功能),也会因为进入范围太宽,而使传送效果过于显目:
为了解决这个问题,我们在第四代让玩家于进入范围内按住上或下时,人物会走到实际的进入点。例如当人物在楼梯底端入口的右侧时,按住上会先让人物走到进入点,然后才上楼梯。为了这个目的,我们需要增加一些只有“玩家人物在进入范围内”条件成立时,才会有作用的额外操作方式。
当然,现在我们不再需要传送了,但也正因为玩家可以穿过楼梯,就导致从另一方向接近楼梯进入点时,人物从一般走路动画切换到上楼梯动画不自然的问题。所以到了第五代,我们把进入范围和穿过检查点结合起来!检查点需要更新成能视玩家人物接近的方向来调整位置,好让人物不论从哪边接近,都能在恰好的位置触发检查点。
总合运作起来如下:若玩家在进入范围内按住上/下,人物会走向进入点,若在人物碰撞区碰到检查点的时候,方向键仍是按住的,就会开始进入到在楼梯上移动的状态。
注:穿过检查点随时都会依据玩家接近的方向,切换到与玩家相对的那一侧,才能让人物的碰撞区与检查点在完全正确的时机接触,并开始在楼梯上的移动。
现在玩家可以穿过每一个楼梯的进入点了。但要是上下楼梯是继续向左或向右的唯一途径呢?如果我们有时候就是要第一代的运作方式,强迫朝着楼梯按住左或右的玩家进入楼梯呢?依照你的关卡编排方式,某些楼梯可能是朝某方向前进的唯一通路,但其他楼梯仅是分支的通路。
因此,我们在第六代分出了两种楼梯进入点(“可穿过”与“不可穿过”)。为此我们加入了一个叫做“可穿过”的布林值,供我们个别套用在每一道楼梯物件的两端上。若某个进入点为不可穿过,那么穿过检查点就不会动态换边,因为该进入点只能从一边接近。
新增这项设计后,我们必须将玩家在意图进入不同类型的楼梯时,可能会使用的按键和按键组合全都纳入考量。在楼梯底端按上,和在楼梯顶端按下,至此已都有预期中的效果了。不过,当从左侧走向不可穿过的楼梯时,“按住右”也会是玩家表达进入楼梯意图的行为(反之亦然),因为这种楼梯不能穿过。现在当玩家触发穿过检查点的时候,只要按住下面个别状况中的特定按键时,人物就会走上楼梯:
这操作起来可能已经感觉不差了,但根据玩家操控的实作方式,如果让玩家在走上楼梯时有好几种按键组合可按,那这又会衍生出另一类的问题了。
为了要在第七代搞定这些事情,我们必须在不同类型的楼梯进入点处定义哪些按键会互相取消,还有哪些按键一起按不能把移动速度叠加上去。我们也必须一个个设定动画,如果有两个按键会互相取消(例如玩家同时按住左和右),那不该造成人物模型原地走动。为了解决所有可能的问题,关键就在于区分上行梯(左下到右上)和下行梯(左上到右下)。举例来说吧,若QQ号买卖平台玩家在下行梯的范围内按住左(远离楼梯)和下(在本例中视同走向楼梯),可能就会导致“原地踏步”的动画。又或者他们会为了移动速度加倍而按住下(走向楼梯)和右(也是走向楼梯)。我们必须将所有类型楼梯可能会遇上的所有按键组合都想清楚,才不会捅出麻烦来。也许各位已经自行想出了能解决这个问题的精妙系统,不然就和我一样,得在实际的移动逻辑之外手动定义一箩筐的布林组合。
我呼吁各位千万不要在这个步骤偷懒,一定要算到所有小细节。这不只能避免出错,也能让移动操控变得更直观。
好啦。走上侧向梯的部分搞定了。那正向梯的部分有什么特殊规则吗?
其实我们只需要加入一些小调整,就可以在第八代加入正向梯了,因为基本的逻辑都通用。在我们的游戏中,做法是让正向梯只有中间一小段宽度可以使用,而非整个楼梯面。因此,它们有两个位置明确的进入点,就和侧向梯一样。这也代表玩家随时可以穿过前梯,意即它们的顶端和底端都是可穿过的,因为楼梯图像的宽度必定比进入范围还宽。当然了,在某些情况下玩家可能穿过正向梯后就立刻遇到墙壁而无法前进。其余部分(走到楼梯上的行为)运作模式如出一辙。更棒的是,我们不需要去顾虑玩家按住左 / 右来尝试走上正向梯的状况。
第二步:楼梯上的走动
恭喜你走到这一步了,值得掌声鼓励一下。我发誓,进入楼梯是这个系统最复杂的部分。现在我们就来定义在各类楼梯上的移动吧。
根据你游戏的设定(例如你导入写实的物理和重力),以垂直方式或对角线方式向上移动可能会造成一些问题,比如说你的人物从上方一走进楼梯就会滑下去。因此到了第九代,我们必须强迫人物在楼梯上贴着一直线线段来移动,而不使用物理碰撞来决定人物的移动或站立的位置。事实上,我们需要在人物进入楼梯时取消所有作用中的力,同时忽略所有在楼梯上期间被施加的力。好在我们的游戏不用考虑战斗,或其他会对人物在楼梯上移动时施加外力的因素。
但我们要怎么确保玩家移动的角度一定能走到底端或顶端?我们可以计算顶端和底端之间的角度,不过既然为了配合人物动画,楼梯的每一阶都已经一样大了,所以不管是长是短,我们的侧向梯都会是同样的角度,那么我们只要写死就行了。
好啦,那我们的移动操控要怎么在楼梯上运作?就到第十代来设计一下吧。玩家会预期上、右和上+右都应该要能走上楼(下楼梯时则相反)。记得不能叠加速度。
再一次地,我们也得考虑会互消的按键组合。例如上楼的时候,左和下是一样的,都应该要能取消视为同样的上和右。
那正向梯上的操控方式呢?就靠第十一代来搞定!
各位可能觉得按上和下不就没事了吗。然而迫使玩家要先按住上或下走到楼梯最末端后,才能左右移动,不仅反直觉,也让人感觉游戏不精致。玩家也可能会想,为什么在离顶端或底端还差 1 像素的地方按下左或右,却是一点反应也没有。因此我们必须分别在底部和顶部 1 公尺的地方定义“离开范围”。玩家在离开范围内按住左或右就会朝着楼梯的“离开点”移动。在抵达离开点后,他们就会走出楼梯并继续朝着他们按住的方向走过去。也许各位注意到了,这就和进入范围运作的方式完全一样,让玩家得以使用另外的按键走向进入点(也就是我们在很前面第三和第四代加入的功能)。还有,我们得考虑会互相取消或叠加速度的按键组合。
加入离开范围后感觉精致了许多,但这样做也另外产生了一些问题,必须使用第十二代来解决。举例来说,如果前梯只有 2 公尺或是更短,那玩家同时处于顶端和底端的离开范围怎么办?那就把离开范围设为楼梯总长的 20%,不要写死成 1 公尺。
这样解决了楼梯短的问题,但楼梯很长的时候又出了新问题,因为 20% 的离开范围实在太长了。所以我们更新了十二代的规则,在第十三代中限制两端离开范围最长各为 1 公尺。
第三步:离开楼梯
就快完成了!现在进入楼梯和沿着楼梯走都很顺利,离开楼梯其实也满简单的。
至前述设计为止,我们的人物会一直延续楼梯上的移动,直到永远:
在第十四代,我们设置了碰撞区让玩家触发的时候可以离开楼梯(也就是回到正常的行走操控,重新施加物理计算)。我们其实重复利用了顶端和底端的进出范围碰撞区:
唯一的问题在于碰撞区过早触发,在顶端的时候尤其严重,这是因为人物碰撞区高度的关系。我们把顶端的碰撞区上挪至足够高度、也将底端碰撞区下移一点,让人物上下楼梯到最末端的时候正好在碰撞区里面。
可是,玩家在进入楼梯的过程中,人物还是在碰撞区里,那他们可以决定不要上楼梯,在不再次触发碰撞区的情况下穿过离开点吗?当然可以。在第十五代中,我们用和进入楼梯同样的方式来解决:我们替所有例子(侧向下梯/侧向上梯/正向梯)一个一个定义离开楼梯的意图,如同第六代那样。当玩家在离开范围内按下任一个合理的按键组合,他们就会离开楼梯。
到了第十六代,我们再次考虑低影格率时,可能会导致玩家冲过离开点的情况。如果你已经使用第二代时提过的,与影格率脱钩的物理系统,这就不会是问题。若否,我们将人物传送到离开点,就如同传送到进入点那样:
大功告成了!只需少少的十六次叠代,我们就成功做出了直观、不易出错又赏心悦目的楼梯动作。
在影片录制后,我们又稍微改善了这个系统。我们改成使用 FixedUpdate() 而非较容易被注意到的传送方式,并新增了在正向梯上站着不动的闲置动画,它会根据先前移动方向而有所变化(面向或背对玩家视点)。
当然我们也很清楚,和那些程式大佬以 AI 为基础所开发的 3D 复合地形移动系统比起来,这种移动系统根本就是小巫见大巫。不过这套系统我们用起来很顺,应该也能轻松在任何类似的 2D 横向卷轴游戏中实作出来。各位若是觉得它还能更简化、更强化、不管什么化之类的,都欢迎提出来和我们交流交流。我们很乐意改善我们的程式和这篇指南,为其他开发者提供助益。
希望这篇文章有寓教于乐的功用,或至少能点出一个道理:看似平淡无奇的机制实作起来可能相当棘手。