内容

 
 

翻译:崔国军(飞扬971)  审校:张乾光(星际迷航)

 


这篇文章的作者是罗德里戈·蒙泰罗,本文最初是发表在他自己的博客“高阶乐趣”上然后才转载到我们的网站上(感谢罗德里戈的分享)以便让更多的人可以看到这篇文章。

由于对这个主题之前可以查阅到的信息感到非常失望,所以我尝试用不同的方法来实现2D平台游戏,我会列出每种方法的长处和短处,并且讨论这些方法的一些实现细节。

我的长期目标是让它成为实现2D平台游戏的一个详尽和全面的指南。如果你有任何形式的反馈、更正、请求或者想对这篇文章增加一些内容-请在这篇文章的评论部分留下你的意见!

免责声明:这里提出的一些信息来自对游戏行为的逆向工程,而不是来自它的代码或者实现这款游戏的程序员那里。所以它们实际上有可能不是以这种方式来实现的,而只是表现的结果相同。还要注意的是瓦片的大小是针对游戏逻辑来说的,图像渲染中的瓦片可能是另外一个尺寸!

 

四种不同的实现方式

我能想到用四种不同的方法来实现一个平台游戏,从最简单到最复杂,它们分别是:

 

第1种类型: 基于瓦片的方案(纯粹基于瓦片的方法)

人物的移动仅限于在有瓦片的地方,所以人物不能站在两个瓦片的中间。动画可以被用来创建平滑运动的错觉,但就游戏逻辑而言,玩家总是在一个特定瓦片的正上方。这是实现一个平台游戏最简单的方法,但这种方法对人物的控制提出了很多严格的限制,使得它不适合传统的基于动作的平台游戏。但是这种方法在解密游戏和“电影类”的平台游戏里面很受欢迎。


来自《Flashback》的截图, 我们吧把瓦片的边界显示出来了。

这种实现方法的例子有:《波斯王子》、《小鸡快跑》、《淘金者》和《Flashback》。

它具体是如何运行的

地图是瓦片的网格,每一个瓦片都存储着信息,比如它是否是一个障碍物、使用什么样的脚步声以及其他一些信息。玩家和其他角色用一组一起移动的瓦片来表示。举个简单的例子来说,在《淘金者》中,玩家就用一个单独的瓦片来表示。在《小鸡快跑》中,玩家用2×2的瓦片来表示。在《Flashback》,由于它瓦片的大小更小,所以它的表示方法是不同寻常的,玩家在站立的时候是2个瓦片宽5个瓦片高(看上面的截图),但是蹲下的时候只有三个瓦片高。

在这种游戏中,玩家将很少-如果有的话–沿对角线进行移动,如果一定要这么做的话,沿对角线进行移动可以分解成两个单独的步骤。同样的,他一次将只可能移动一个瓦片的距离,但是如果需要的话,多个瓦片的一次移动可以通过多次一个瓦片的移动来实现(在《Flashback》中,你将每次移动2个瓦片)。这个算法的具体过程如下:

  • 在角色想要移动到的位置创建角色的一个拷贝(举个简单的例子来说,如果角色想要往右移动一个瓦片的话,那么就会创建这个角色的一个拷贝,这个拷贝中角色的每个瓦片都会往右平移1个瓦片)。
  • 检测角色的拷贝是否与背景或者其他角色是否发生碰撞。
  • 如果发现了一个碰撞,那么角色的移动就将被禁止。并做出相应的反应。
  • 如果没有碰撞的话,说明整个路径都是干净的可以移动的。那么就把角色移动到那里,可以选择播放动画以便让整个过渡看上去非常平滑。

这种类型的运动对于传统的圆弧形状的跳跃来说是非常不适合的-所以这一流派的游戏往往根本就没有跳跃(比如说《小鸡快跑》、《淘金者》就根本没有跳跃),或者只允许垂直或者水平的跳跃(比如说《波斯王子》和《Flashback》就是这种情况),这种跳跃不过是直线运动的特殊情况。

这种系统的优点包括简单性和精确性。由于游戏更具有确定性,小故障小失误会少的多,而且整个游戏体验更加可控,很少有根据环境来调整设置值的需求。实现某些特殊机制的时候(比如抓住墙壁或者单向平台)的时候变得非常的容易,相比于更复杂的运动风格 -所有你要做的就是检查玩家所在的瓦片与背景瓦片是否以某个特殊的方式对齐来允许执行这样一个特殊动作。

原则上,这种系统不允许小于一个瓦片的步骤,但可以通过几个不同的方式来减轻。举个简单的例子来说,瓦片可以比角色的大小小一点(比如,一个玩家的大小是2*6个瓦片),或者可以在一个指定的瓦片上允许只有视觉改变的运动而完全不改变逻辑。我相信这就是《淘金者-传奇归来》里面使用的解决方案,这个方案的意思是在渲染的时候更改角色的位置,但是在逻辑上不动,这样逻辑上还是不允许小于一个瓦片的移动,但是在表现上就允许,而且可以移动的距离可以任意小,可以支持各种效果,给玩家一个错觉就好了。

 

第2种类型: 基于瓦片的方案(平滑的方法)

碰撞仍然是由瓦片地图决定的,但是角色可以在整个世界自由移动(通常最小的移动精度还是1个瓦片,对齐到整数,但是看下这篇文章结尾的笔记说明里面提到的平滑运动)。这是8位和16位游戏机平台游戏实现的最常见的形式,今天仍然非常流行,因为它非常的容易实现,并且使得关卡编辑比其他复杂的技术方案更加容易。它还支持斜坡和平滑跳跃弧线。

如果你还不确定你想实现哪种类型的平台游戏,并且你想做的是一款动作游戏,我建议你就使用这种方案。这种方案非常灵活也相对容易实现,并且是四种实现方案里面提供最大自主控制权的。这也是为什么在整个游戏时代大部分最好的动作平台游戏都是使用的这个方案。


《洛克人X》,将瓦片的边界和玩家的受击盒显示出来的效果。


让我们举一些使用这个方案的例子:《超级马里奥世界》、《刺猬索尼克》、《洛克人》、《超级银河战士》、《魂斗罗》、《合金弹头》以及16位游戏机时代的每一个平台游戏。


它具体是如何运行的

这种方案的地图信息的存储和纯粹使用瓦片的方案的地图信息的存储是完全一样的,所不同的仅仅是角色是如何与背景进行交互的。角色的碰撞受击包围盒现在是一个轴对齐包围盒(也就是一个不能旋转的矩形),且通常情况下仍然是瓦片大小的整数倍。通常情况下,角色的碰撞受击包围盒的大小包括1个瓦片宽1个瓦片高的包围盒(《小马里奥》、《变形球》是这种情况),也有1个瓦片宽2个瓦片高的包围盒(《大马里奥》、《洛克人》是这种情况),还有1个瓦片宽4个瓦片高的包围盒(站立状态就是这种情况)。在很多情况下,角色的精灵本身比逻辑的碰撞受击包围盒大一些。这样做是为了一个更好的视觉体验和更公平的玩法(对玩家来说,在他应该碰上的时候避免碰上要比不该碰上的时候碰上整个游戏体验更好些)。在上图中,你可以看到往 X 方向的精灵是正方形的(事实上,是两个瓦片宽), 但他的碰撞盒却是一个矩形(只有一个瓦片宽)。

如果假设游戏里面没有斜坡和单向平台的话,整个算法是非常直观的:

  • 将运动分解到 X 和 Y轴上,一次只能前进一步。如果你计划随后实现斜坡的话,先从X轴开始,然后才对Y轴做实现。否则的话,顺序其实根本无所谓。然后,对每个轴进行处理:
  • 得到位于前方的边缘的坐标,举例说明下:如果角色是在向左走,那么这个坐标是包围盒的左侧的x坐标,如果角色是在向右走,那么这个坐标是包围盒的右侧的x坐标,如果角色是在向上走,那么这个坐标是包围盒的上侧的y坐标,以此类推。
  • 计算包围盒和哪一个瓦片的哪些线相交--这将在相反的坐标轴上给你一个最小瓦片的值和最大瓦片的值。举个简单的例子来说,如果角色正在向左走,角色可能会与水平线方向的32,33,34行相交(也就是,符合y=32*ts,y=33*ts和 y=34*ts的区瓦片会与角色相交, 这里ts等于瓦片的大小)。
  • 沿着这些瓦片的线向着角色运动的方向扫描,直到你发现最近的静态障碍,接着,对每一个运动的障碍进行遍历,来判定哪一个障碍是在角色实际路线上最接近的障碍。
  • 沿着这个方向,玩家的角色总的运动距离就是玩家角色和最近的障碍之间的最小值,而这个量就是你的角色需要移动到的第一个点。
  • 移动玩家的角色到新的位置。如果仍然没有完成的话,通过这个新的位置,开始计算计算另一个位置来让玩家的角色移动到这。

斜坡


《洛克人X》,附带了对斜坡的注解。

对斜坡(上图中绿色箭头指向的那些瓦片)的处理可能非常棘手,这是因为它们本身是障碍,但你还得允许角色运动到它们这些瓦片里面。它们也往往导致角色在沿着x轴的运动还需要调整角色在y轴的位置。一种处理这种情况的方式是让这些瓦片存储每个边的“最底下那个边缘的Y坐标” (floor Y)。假设一个坐标系,它的原点(0,0)在左上角,这样x坐标左侧的这个瓦片的存储的Y 坐标是(第一个斜面)是{0,3},接着下一个瓦片它代表的是{4,7},然后是{8,11},再然后是{12,15}。在这之后,另一个瓦片代表{0,3},等等,这样我们就得到了一个比较陡的斜面,这个斜面由两个瓦片组成:{0,7}和{8,15}。


{4,7} 瓦片的放大视图
我在这里想描述的这个系统是允许任意斜面的,尽管从视觉因素上看,这两个斜面是最普通的,结果是导致了需要用12个我瓦片(前面描述的是6个,还有他们的镜像映射也是6个)来描述。 碰撞算法需要作如下修改:

l确保你的角色在Y方向上的位置变化之前先变化X方向上的位置。

l在碰撞检测的过程中,如果这个斜面的最近边缘是相对较高的(也就是说Y值更小)话,那么这个斜面仅仅算作一次碰撞。这样可以防止角色从相反的方向穿过斜面。

l你可能希望禁止在斜面上停止所谓的“半途穿越”,(例如,在一个{4,7}的瓦片上),这个限制被《洛克人》和其他很多游戏采纳。如果不这样做的话,在玩家尝试从低端的一侧往高的那一侧爬的时候,你将被迫处理很多更加复杂的情况,一种处理这种问题的方式是预先处理这个地方,并且标志所有相关的区块。那么,在碰撞检测时,从低的一侧出发的玩家如果他的最低y坐标比这个区块的边的距离差要大的话(也就是位置比高的那一侧低),那么这些瓦片也要参与碰撞计算(tilecoord * tile size + floor y)。

l玩家的角色当前站立的与斜面相邻被障碍占据的瓦片不应该参与碰撞计算,如果它与这个斜面相连的话。这样的话,如果角色(这里说的坐标,都是指的角色下方中心点的坐标)是在一个{0,*}的斜面上,那么就忽略角色左边的瓦片,如果角色(这里说的坐标,都是指的角色下方中心点的坐标)是在一个{*,0}的斜面上,那么就忽略角色右边的瓦片。如果你的角色比两个瓦片宽的话,那么你需要对更多的瓦片作这样的处理-如果玩家的角色正在想斜面的高的那一侧移动的话,你可能需要跳过对整个行的检测。这样做的原因是为了防止角色一直爬斜面的时候身体会进入这些瓦片(上图中黄色高亮的部分),因为角色的脚仍然低于表面的高度的时候,角色就与其他瓦片发生碰撞。

在垂直移动的时候碰撞算法如下:

  • 如果你要让角色在下坡的过程中重力发挥作用的话,需要确保最小的重心偏移与坡度和水平速度兼容。举个简单的例子来说,就是在一个4:1斜率的斜坡上(就好像{4,7}以上的部分),重心偏移必须至少是水平速度的1/4,并且进行取整。如果是在一个4:1斜率的斜坡上就好像{0,7}的部分),心偏移必须至少是水平速度的1/2,并且进行取整。如果你没有办法确保这一点的话,玩家的角色将在水平移动一会就要倾斜,直到重力的影响赶上来再将角色的方向拽回去,这会使角色在坡道上反弹,而不是向下顺畅的下降。
  • 替代重力的方法是计算玩家在移动之前有多少像素在地平面之上,在移动以后又有多少像素在地平面之上(可以使用下面的公式进行计算),然后调整角色的位置让这两个值相等。
  • 当玩家的角色向下移动的时候,不会考虑斜面的上部边界作为它的碰撞边界,而是计算当前垂直线的最下面的坐标并且使用这个值。要做到这一点,要找到[0,1]值来表示(0 =左侧,1 =右侧)玩家角色的x坐标在瓦片上的位置,并利用这个值来对floorY的值进行线性插值。代码看上去是这样的:
    float t = float(centerX - tileX) / tileSize;
    float floorY = (1-t) * leftFloorY + t * rightFloorY;
  • 当玩家的角色向下移动的话,如果同一个Y坐标的多个瓦片都有可能是障碍的话,玩家中心X坐标的位置所在的瓦片又恰好是一个斜面瓦片的话,就只考虑这个斜面瓦片来参与碰撞计算,忽略掉其他的瓦片-即使别的瓦片可能离角色更近。这可以确保当玩家的角色在斜面瓦片周围活动的时候运动正确性。

单向平台

这个截图来自《超级马里奥世界》,显示的是马里奥在同一个单向平台上的掉落(如左图)和站立(如右图)。

单向平台是这样一种平台,你既然可以站在上面,也可以从这上面掉落。换句话说,如果你已经在这个平台的上面,这么这个平台就算是一个障碍,否则这个平台就是可以通行的。这句话是理解这种行为方式的关键。算法要相应地做如下改进:

·在x轴上,这个瓦片永远不是一个障碍。

·在y轴上,这个瓦片只有在在如下这种情况下是障碍:如果在运动之前,玩家完全站在这个平台上面,(这样,玩家的最底部的坐标至少比一个单向平面的最上方的坐标高一个像素)。为了检查这种情况,你可能想要在玩家角色做任何运动之前存储玩家角色的原始位置信息。


如果玩家的角色速度是正的(也就是说,如果玩家的角色在下落),这样可能会让人想把单向平台当作一个障碍,但这种行为是错的:玩家可能跳跃,所以他越过了这个平面,但这样的话再次下落并没有让他落在这个平台上,在这种情况下,他应该仍然能够穿过平台。

 

有些游戏允许玩家的角色从这样的平台上往下跳,有几种方法可以做到这一点,但都是相对简单的方法。举个例子来说,你可以在某一帧中关闭单向平台的功能,并保证角色y方向的速度至少是1,(这样,这个角色将在下一个帧中清除初始的碰撞条件),或者,你可以检查角色是否完全站在一个单向平台上,如果是这样的话,手动移动这个角色到离底部一个像素的距离。


梯子


这是《洛克人7》的截图,把每个瓦片的边界显示出来了,高亮了梯子的瓦片以及玩家在攀爬梯子时候的受击包围盒。

 

梯子看起来很难实现,但实际上梯子也不过是一个简单的状态变换而已,--当角色在一个梯子里面的时候,忽略掉大多数的标准碰撞系统,用一个新的规则进行替代。梯子一般是只有一个瓦片宽。


通常可以通过两种方式进入梯子状态:

l让你的角色的碰撞盒与梯子进行重叠,无论是在地上还是在空中,然后点击向上走(有些游戏允许点击向下走)。

l让你的角色站在"梯子头部"瓦片的上方,(这常常也是单向平台所在的瓦片,所以,你可以在它上面走),然后点击向下走。

 

这样就产生了一个效果,立即把玩家的x坐标和梯子所在瓦片关联起来,这样,如果角色是从梯子的顶端往下走的话就移动y坐标,玩家现在实际上就在梯子里面。从这一点来说,一些游戏采用了一个不同的碰撞盒去判断玩家是否仍然在梯子里面。举个简单的例子来说,《洛克人》游戏看起来就是使用了一个单独的瓦片(与初始角色的顶部的区块等价,也就是上图中高亮的红色区域)。

离开梯子有几种不同的方式:

l到达梯子顶端。这里通常会提供一段动画并且沿着y轴方向移动角色几个像素,所以,现在角色站在梯子的上方。

l达到梯子的底部,这样将使得角色简单地下滑,尽管有些游戏不让角色用这种方式离开梯子。

l向左或向右移动,如果在那个方向上没有障碍,角色将被允许用这种方式离开。

l跳跃,有些游戏允许你用这种方式离开梯子。

当角色在梯子里的时候,他的运动方式会以这种方式改变,一般来讲,他所能做的就是向上或者向下移动,有时候会攻击。

楼梯


这是《恶魔城:血之轮回》的截图,把瓦片边界显示出来的效果。

楼梯是梯子的一个变体,在有些游戏中能看到,但在《恶魔城》系列里十分显眼。实际的实现和梯子的实现非常相似,只有几个例外:

l玩家一个瓦片一个瓦片地移动或者半个瓦片半个瓦片地移动(就想在《恶魔城:血之轮回》里面角色的移动方式一样)。

l每移动一“步”,都会导致玩家沿x轴的坐标和沿y轴的坐标同时改变了预先设定的量。

l在往上走到时候,最初的重叠检测可能看起来像在瓦片的前方而不是仅仅对当前瓦片进行重叠检测。

其他游戏也有看起来行为像是斜面的楼梯,在那种情况下,它们之间的区别仅仅是视觉作用。



运动的平台


来自《超级马里奥世界》的截图

移动的平台看起来有一点棘手,但实际上却是相当简单的。不像通常的平台那样,移动的平台不能被用固定的瓦片来表示(原因是显而易见的),实际上应该用一个轴对齐矩形边界框表示,这样其实就是一个不能被旋转的矩形。从所有的碰撞意图来看,它都是一个正常的障碍,如果角色在这里停止,角色将站在非常光滑的移动平台上(这样的话,它们可以实现的像预期的那样工作,除了角色不能按照自己的意图进行移动以外。)。

有几种不同的方式去实现移动的平台。其中的一种算法如下:

l在屏幕上的任何东西进行更新之前,先检测角色是否站在一个移动的平台上面。这个可以通过检测来实现,举个简单的例子来说,角色的中心的底部像素是否仅仅在这个平台的上方一个像素的位置。如果是这样的话,储存一个指向这个平台的指针,并且它当前的位置在角色内部。

l对所有的移动的平台进行更新,确认这些更新在你更新角色的信息之前发生。

l对每一个站在移动的平台上的角色计算这个平台的增量位置,也就是说,到底沿着每个坐标轴移动了多少距离。现在,用相同的量来移动角色。

l像通常那样更新角色。 

其他有特色的功能

来自《刺猬索尼克》的截图

其他游戏有一些更加复杂和独特的功能。刺猬索尼克系列在这点上很突出。但是这些复杂和独特的功能已经超出了本文的范围(以及我的知识范围),但这些复杂和独特的功能可能是未来的文章的主题。

 

第3种类型: 基于位掩码的方案

这种方案与基于瓦片的方案(平滑的方法)类似,但会使用一些更大的瓦片来表示游戏,每一个像素都使用了一个图像来进行检测碰撞。这样允许更好的处理细节,但也显著地提高了算法复杂度、内存使用量以及要求某种类似图像编辑器的方法来创建关卡。它也常常暗示哪些瓦片不能用于视觉目的,所以,每一个关卡都可能要求大量的、独立的艺术工作。基于这些问题,这相对而言不是一个常见的技术,但能制造比基于瓦片的方案更高的质量效果。它也适合于创建动态环境,就像在《百战天虫》游戏里面那样的可以被破坏的场景。因为你能在位掩码里面“画”一些隐藏的信息来改变场景。


截图里面的地面就是可以被破坏的

使用这个方案的一些例子有:《百战天虫》、《Talbot’sOdyssey》



这种方案是如何工作的?

这种方案的基本思路与基于瓦片的方案(平滑的方法)非常类似--你可以简单地把每一个像素当作一个瓦片,然后去实现完全相同的算法,所有的一切都工作的很棒,只有一个明显的例外-斜面。因为斜面现在是被瓦片附近的位置隐式地定义的,之前的技术就不能使用了。这个地方需要使用一个复杂的多的技术。像其他的东西,比如梯子,也变得棘手了。

斜面

这是来自《Talbot’s Odyssey》的截图,用于碰撞的位掩码重叠在游戏的最上方。

斜面是为什么这种方案实现起来非常困难的首要原因。不幸的是,这种方案也是相当程度地具有强制性的,不使用斜面的话这种实现方案就没有什么意义,通常来说,斜面也是为什么使用这个系统的原因。

我们粗略的介绍下《Talbot’s Odyssey》游戏实现斜面所使用的算法:

 

·整体地考虑加速和速度因素,计算需要的增量位置信息的向量(角色在每个坐标轴上移动了多少距离)。

·对每个象限单独处理,从绝对值最大的那一个开始。

·如果是水平运动的话,偏离玩家轴坐标对齐包围盒的顶点3个像素的距离,这样角色能够在斜面上攀爬。

·扫描前方,通过检测所有有效的障碍和位掩码本身,来确定在碰上一个障碍之前有多少像素可以移动。然后把角色移动到这个新的位置。

·如果这是一个水平运动,移动尽可能多的像素(这里需要增加到3)去适应斜面。

·如果这是移动的最后处理,角色的任何像素如何和任何障碍重合的话,在这个坐标轴上的移动都要撤销。

·不考虑最终的结果。在其他坐标轴上做同样的操作。


由于这个系统不会根据角色是做想下运动还是做降落来进行区分,为了能够决定角色是否能跳跃和改变动画,你可能需要一个系统计算经过了多少帧角色才最后接触了地面。对Talbot这款游戏来说,这个值是10帧。

这里另外一个比较棘手的问题是计算角色在碰上什么东西之前角色能移动多少像素时候的效率问题。这里面还有一些其他可能的复杂因素,像单向平台(用基于瓦片的方案(平滑的方法)完全相同的处理方法)和大坡度斜着向下滑动(这个技术相当的复杂,已经超出了本文的范围)。总的来说,这个技术要做相当多的细节调整,并且明显地不如基于瓦片的方案(平滑的方法)稳定。 我建议你只在需要必须有细节化的地形时才使用这种方案。

 

第4种类型: 基于向量地图的方案

这种技术采用了向量数据(线或者多边形,比较典型的是使用可伸缩向量图形)来判断碰撞区域的边界。虽然合适的实现非常困难,但由于物理引擎的大行其道,这个方案还是很受欢迎的,比如Box2D物理引擎就非常适合实现这种方案。这种方案既提供了类似位掩码技术方案的优点,但是没有那些内存占用的缺陷,还很便于进行图层编辑。

 


这张截图来自《时空幻境》的关卡编辑器,上面是可见的层级,下面是用于碰撞的多边形。
使用这个方案的一些例子有:《时空幻境》、《地狱边境》。

这种方案是如何工作的?

通常有两种方法来实现这种方案:

·你自己来解决移动和碰撞方面的问题,类似于位掩码方案(方案3),但是使用多边形的角度来计算反射或者滑动的斜面。

·使用物理引擎(例如BOX2D)。

很明显,第二种实现方法更加流行(尽管我怀疑《时空幻境》使用了第一种实现方法),这是因为第二种实现方法更简单,同时第二种实现方法还允许你通过使用物理引擎在游戏中作更多其他的事情。不幸的是,按我的观点,使用第二种实现方法的话必须非常小心,如果仅仅通过默认设置开发,那么你的游戏将和其它同款引擎开发出来的游戏极度近似,变得毫无特色可言。

复杂的物体

这种方法有它自身的问题。它可能使得判断玩家是否真的站在地板上(由于舍入错误)或者玩家是否碰上了一面墙或者玩家石佛偶滑下了一个陡峭的斜面变得困难。如果使用一个物理引擎,摩擦力可能是一个问题,比如你期望摩擦力在移动的时候比较大、滑行的时候比较小之类的。

有不同的方式去处理这些问题,但是一个流行的处理方法是把一个角色分成几个不同的多角形,每一个多角形有与一个不同的角色关联:这样你需要(可选的)有一个主干躯体,接着是两条细长的腿,然后要有两个瘦矩形作为侧面,另一个作头部或者什么类似的连在一起的组件。有时他们被缠在一起以避免撞上障碍物。他们可能有不同的物理属性,并且对他们的碰撞回调可以被用于检测角色的状态。为了得到更多信息,场景(未碰撞的物体可以被用于检查重合)可以被应用。通常情况下包括检测角色是否离地面近到足够可以开始一次跳跃,或者角色是否撞到墙上了,等等。

全局的考量

不论你到底选择了哪种方案来进行2D平台游戏的开发(可能第一种方案是例外),你都必须考虑一些全局的因素。


加速状态


左边是《超级马里奥世界》的截图(这是一个加速比较低的例子),中间是《超级银河战士》的截图(这是一个加速中等的例子),右边是《洛克人7》(这是一个加速比较高的状态)。

影响平台游戏的体验的最重要的几个因素之一就是角色的加速度。加速度是速度的变化比率。当加速度很低的时候,角色需要花费很长的时间来达到最高的速度,或者在角色失去控制的时候产生一个停顿,这样使得角色看起来感到“滑动”,而且很难把握,这种运动最普遍地应用在《超级玛莉》系列游戏中。当这个加速度很高的时候,角色花费很少的时间(甚至不需要时间)去从0变到最大值,然后回来,这就导致需要玩家有非常快的反应,就如同“巫术”控制一样, 这种运动可以在《洛克人》系列游戏中看到,(我相信洛克人》实际上采用了无限大的加速度,这样,你要么停止,要么全速)。

即使游戏在水平运动时没有加速度,也有可能至少在做弧线跳跃的时候有一些加速度,否则,角色将会被变形,就仿佛一个三角形一样。



这种方法是如何工作的?

加速度的实现实际上是相当简单的,但是在实现过程中有几个陷阱需要小心。

 

l确定x方上向的目标速度。如果玩家没有接触控制器的时候这个值可能为0,如果玩家按的是控制器的左边那么就是负最大速度,如果玩家按的是控制器的右边那么就是最大速度。

l确定y方向上的目标速度。如果玩家正站在一个平面上的话这个值可能为0,否则的话这个值就是 +terminalSpeed。

l对每个坐标轴,或者使用加权的平均值或者使用加速度递增法来从当前速度加速到目标速度。

 

两种加速方法具体的实现如下:

l加权平均法: 加速度定义为某个数值(不妨称它为“a”), 取值从0(速度不变)到1(瞬时加速)。使用这个值在当前速度和目标速度之间做线性插值,然后设置这个结果为当前速度。

1
2
3
vector2f curSpeed = a * targetSpeed + (1-a) * curSpeed;
if(fabs(curSpeed.x) < threshold) curSpeed.x = 0;
if(fabs(curSpeed.y) < threshold) curSpeed.y = 0;

l使用加速度递增法: 我们将测算对哪个方向增加加速度 (引入符号函数, 如果 numbers >0 就返回1,如果numbers <0 就返回-1 ), 然后检查是否越界。、

 

1
2
3
4
5
6
7
vector2f direction = vector2f(sign(targetSpeed.x - curSpeed.x),
sign(targetSpeed.y - curSpeed.y));
curSpeed += acceleration * direction;
if(sign(targetSpeed.x - curSpeed.x) != direction.x)
curSpeed.x = targetSpeed.x;
if(sign(targetSpeed.y - curSpeed.y) != direction.y)
curSpeed.y = targetSpeed.y;

 

在角色开始移动之前就把加速的因素计算到速度中是一个不错的主意,否则,你将会有一帧的延迟来对角色的输入进行反应。

当角色碰撞到障碍的时候,把角色沿着相应的坐标轴方向的速度变为0是一个好主意。

跳跃控制




来自《超级银河战士》的截图, Samus正在做“太空跳跃” 

在平台游戏中的跳跃可以像检查玩家是否在地面上那么简单(或者,通常来说,玩家在过去的n帧中是否在地面上),如果是这样的话,那么久给这个角色一个初始为负的y方向上的速度(用物理学的术语说,一个冲量)。然后让重力做剩下的事。

有四种比较常见的方式来实现玩家控制跳跃:

l冲量:就像在《超级马里奥世界》和《刺猬索尼克》游戏中看到的那样,跳跃保持了角色在跳跃前的冲量(或者用游戏中实现的方式来说,就是保持了角色的速度。)。在一些游戏中,这是唯一的影响跳跃的弧度的方式了,当然这种方法就像在真实世界中一样。这里也没有什么可补充的,--它就应该是这样子的除非你想做点什么来阻止这种实现!

l空中加速:尽管在大多数游戏中这种实现方法看起来有些不符合物理规则,但这是一个非常流行的有特色的功能,因为这样就使得角色容易控制多了。除了《波斯王子》这一类的游戏以外,几乎每一个平台游戏都有这个功能。通常来说,空中的加速度会迅速降低,所以,推动力很重要,但有些游戏(比如像《洛克人》系列)给玩家完全的空中控制角色的能力。一般的实现方式是当角色在飞行中的时候可以通过操作来修改加速参数。

l空中高度的控制:这是另一个看起来不符合物理规则的动作,但是同样非常的流行,因为这能给玩家更大的对角色的控制权,玩家按住跳跃按钮的时间越长,角色就会跳的越高。典型的实现方式是通过在按下按钮的过程中连续不断地给角色增加推动力(尽管这个推动力会急速的降低),或者通过在按钮按下的时候降低重力来实现的。时间上的限制必须是强制的,除非你希望你游戏中的角色能跳的无限高。

l连续跳跃:一旦有了空中加速以后,有些游戏就允许玩家再次跳跃,可能不限制次数(就像《超级银河战士》游戏中的空中跳跃,或《 Talbot’s Odyssey》游戏中的战斗),也可能在触地之前限制跳跃的次数(“两次跳跃”成了最普遍的选择)。这个可以通过保持一个计数在每次跳跃的时候增加来实现,这个计数会在角色落在地面的时候清零(在你维护这个地方的时候需要小心,或者你可以在第一次跳跃后立即重置)。如果这个量足够低就只允许跳的更远一些的跳跃。有时候,第二次跳跃会比第一次跳跃要短一点。其他的限制条件也有可能被采用,在《太空跳跃》这款游戏中,只会在角色完成一个旋转跳跃并且刚刚开始下落时才会触发。

 

动画和引导


《黑色荆棘》,角色在向后射击之前会做很长的动作(按Y键)

在许多游戏中,你的角色在实际执行你请求的动作之前会先播放一段动画。然而,在一个让人焦躁不安的动作游戏中,这将使玩家感到非常沮丧,请不要这么做!(别在角色实际执行你请求的动作之前播放一段动画) ,你仍然可以在诸如跳跃和跑动之前放一段引导动画,但如果你在意游戏是怎样给玩家反馈的话,那么就仅仅把这个当作点缀,同时要立刻执行动作而不会考虑播放什么动画。

平滑的运动

使用整数来表示角色的位置是非常明智的,因为这种表示方法使得开发很快而且得到的结果也非常的稳定。然而,如果你对任何东西都使用整数的话,最终将导致忽动忽停的动作。针对这个问题有很多的解决方案,这里是其中的几个方案:

l对所有的计算和储存位置都使用浮点数,并且在任何需要渲染或者计算冲突的时候将浮点数转成整数。这样做又快又简单,但是如果你从原点出发移动到很远地方的时候会开始降低准确性。这可能没有什么关系除非你有一个非常大的游戏区域。但你需要留意这个问题,如果这个问题发生了,你可以使用一个双精度浮点型来代替。

l对所有的计算和位置使用一个固定小数点位数的分数,并且在任何需要渲染或者计算冲突的时候将浮点数转成整数。这样的话,相比较浮点数的方案精度会降低,范围会更小,但是这种精确度是统一的,对有些硬件会更快(很明显,浮点数运算在很多手机上是很慢的)。

l把位置信息储存为整数,但保留一个“剩余”的部分,并将它储存为一个浮点数,当计算位置的时候,计算增量运动的结果并用一个浮点数来表示这个结果,把这个剩余部分算到这个量运动中去,然后,把这个值的整数部分加到表示位置的值中,而把这个值的剩余部分放到“剩余”域中。在下一帧中,剩余部分会再被加回来。这种方法的优点是除了运动之外的所有地方都使用整数,保证其他地方你不会遇到处理浮点数的麻烦,同时提升了游戏性能。这种技术也非常适合在架构上要求这个位置的对象的一些结构必须为整数的情形,或者这个数据是一个浮点数,但是它要直接被渲染系统调用,--在这种情况下,你可以使用这个架构提供的浮点位置仅仅存储整数值,确认渲染系统使用的值总是和像素对齐的。

 

 

【版权声明】

原文作者未做权利声明,视为共享知识产权进入公共领域,自动获得授权。