聊聊 2D 游戏视差背景的实现

引言

这次聊什么呢?因为我们正在实现一个横版游戏,咱们这次就聊聊横版游戏里非常重要的技术——视差背景。

作为横版的 2D 游戏而言,如果想要提升画面效果,想必很多人想到的就是做视差背景。也因此市面上大部分横版游戏多多少少都会使用视差背景来增强游戏画面的视觉效果:
 

聊聊 2D 游戏视差背景的实现

泰拉瑞亚的视差背景,是比较简单的平铺背景,没有太多特效

聊聊 2D 游戏视差背景的实现

Braid 中也使用了视差背景,但要比一般游戏的复杂很多

这里我们主要对其中的技术细节进行介绍。

什么是视差背景?

简单地说,视差背景其实就是通过多层次的背景来模拟透视视差效果:就是当发生移动时,离照相机越近的背景移动越快;反之越慢。这样,我们的背景就会形成类似于透视视差的效果。

那么,既然需要透视效果,为何不直接使用透视投影来做呢?这个原因是如果使用透视投影来产生视差的话,我们的远景必须真的是一个非常大的背景,你如果想模拟出一百倍于近景的远景,那么可能就需要相应尺寸的背景贴图。这种做法显然是做不到的。当然,如果是3D 背景的话有其他方式,不过对于2D 游戏而言,最直接有效的还是多层次背景模拟出视差效果。我们这里也主要聊聊如果通过多层次背景滚动的方式实现视差效果。

在整个视差背景实现过程中,需要完成两个主要工作:
 

  • 实现单层背景的滚动;
  • 复合多层背景的滚动,实现视差效果;



实现单层背景的滚动

背景滚动是实现视差效果的核心也是最重要的问题。背景滚动存在横向和纵向两种。所有使用视差背景的游戏都会有横向滚动的情况,而纵向滚动则未必都会有。我们这里以横向滚动来介绍背景滚动。我们有四种常规方式可以实现背景的滚动:
 

  • 通过移动一个四边形顶点的 UV 移动形成滚动,之后就称之为 UV 滚动方式;
  • 通过滚动移动多个连续的背景精灵形成滚动,之后就称之为精灵滚动方式;
  • 添加背景层照相机,移动照相机形成滚动,之后就称之为照相机移动方式;
  • 精灵滚动方式和照相机移动方式混合使用,之后就称之为混合滚动方式;



为了更好地解释这几种实现方式,需要几张图片用于介绍:

我们使用一个黄框精灵代表屏幕取景区域:
 

聊聊 2D 游戏视差背景的实现


接下来是三张可拼接的背景精灵:
 

聊聊 2D 游戏视差背景的实现


UV 滚动方式:

使用 UV 坐标移动形成滚动的效果看起来是这样的:
 


在 UV 坐标移动的方法中,我们只是用一个和照相机取景区域一样大的精灵作为背景渲染区域。然后通过调整它的 UV 坐标和采样方式实现平铺背景以及背景滚动。具体实现步骤:
 

  • 准备一个覆盖整个屏幕的四边形顶点,并使用它显示背景贴图;
  • 调整 UV 坐标和纹理之间的采样方式,以实现纹理连续显示;
  • 移动时,修改四个顶点的 UV 坐标形成滚动;



因为我们只使用一张精灵,我们区域采样的方式就是通过 UV 坐标。因此此方式下 UV 坐标存在两个作用:
 

  • UV 坐标的整数部分标记了当前采样位置使用哪一张背景贴图;
  • UV 坐标小数部分为选中背景贴图的采样 UV 坐标;
  • 因此,此精灵的 UV 坐标必定会大于[0,1]区间。



如果背景使用的背景贴图只有一张的话,这个问题很容易解决。我们只要设置图形 API(OpenGL 或 DirectX,这里以 OpenGL 为例)的纹理包装类型(即所谓的 wrapping 类型)即可。所谓的包装类型即指定了当 UV 坐标值在[ 0,1 ]区间之外时,如何获取纹理。那么这里,我们需要让一张纹理重复出现。在 OpenGL 中,我们需要调用此函数来完成包装类型的设置:glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );稍微解释一下此函数:glTexParameteri 函数会对指定的纹理的参数进行设置。我们这里针对2D 纹理(第一参数 GL_TEXTURE_2D)的 x 方向即横向(第二参数 GL_TEXTURE_WRAP_S)的包装模式设置为重复出现的方式(第三参数 GL_REPEAT)。

不过如果背景是多张不同的纹理连续出现的话,就不能使用上面的方法解决了。这个时候我们需要编写一个简单的 shader 在 shader 中完成 UV 坐标映射。手机游戏转让这里截取出演示 demo 中,获取纹理相关的 shader 代码(GLSL 代码)提供大家参考:

  1. //我们有三张背景贴图,对 UV 坐标对3取模,得到使用哪一种背景贴图。            
  2. int _index = int( mod( v_TexCoord.x, 3.0 ));
  3. //使用此贴图并使用 UV 坐标的小数部分进行像素采样。            
  4. gl_FragData[0] = texture( u_BackgroundTextures[_index], fract(v_TexCoord));  
复制代码


UV 方式有着非常良好的性能,但是缺点就是只能处理简单的平铺背景,对于有着复杂结构或是效果的滚动背景没有办法使用。

精灵滚动方式:

使用精灵滚动形成滚动的效果看起来是这样的:
 


这种方式应该是比较直接的。我们首先使用背景精灵拼接出背景取景区域覆盖到的背景区域。然后在发生背景移动时,我们依然不需要移动背景取景区域,而是通过滚动移动所有的背景精灵来实现背景移动的效果。

这种方式实现简单直接,但缺点是发生背景移动时,需要对所有的背景精灵进行移动。对于结构复杂元素较多的背景需要占用更多的性能。

照相机移动方式:

使用照相机移动形成滚动的效果看起来是这样的:
 


这种方式与精灵滚动方式正好相反。在这种方式下,我们需要使用背景精灵拼接出完整的背景。同时在背景移动时,不移动背景精灵转而移动背景取景区域来实现背景移动效果。

这种方式在移动过程中由于只需要移动背景照相机,所以有种很好的移动性能。但是为了使用此方法。我们需要预先将整个背景全部拼接。这样导致同时存在过多的背景精灵在场景中。如果使用的游戏引擎没有场景管理器或是场景管理器性能不佳的情况下,此方式反而会带来额外的性能消耗。

混合滚动方式:

使用混合滚动方式形成的滚动效果看起来是这样的:
 


顾名思义,混合移动混合了精灵移动和照相机移动两种方式。我们在移动背景取景区域的同时,适时地滚动背景精灵。使得背景取景区域内的背景正确。

这种方式结合了精灵滚动方式和照相机移动方式。避免了精灵滚动方式移动过程中,因为需要移动所有背景精灵带来的额外性能开销;也避免了照相机移动方式中,需要预先构建完整的背景而导致场景中存在过多的背景精灵带来的额外性能开销。当然,和照相机移动方式一样,避免不了每一层背景都需要有一个独立的背景照相机。同时在代码实现良好的情况下,性能比前两者都要好。

四种方式的优劣

平均性能:

UV 滚动方式只使用了一个四边形并且移动时也只是单纯改变了 UV 采样方式。它的性能是最好的;其次是混合滚动方式;照相机移动方式有更多的空间开销,同时此开销对性能的影响与游戏引擎的场景管理模块密切关联;精灵方式则有最大的移动性能消耗。

对复杂背景的支持:

即四种方式所实现的背景可以有多复杂。UV 滚动方式碍于实现只能做简单的平铺背景的滚动效果;精灵滚动方式和混合滚动方式可以实现更为复杂一点的背景,可以在简单的平铺背景之上加入一些其他背景精灵元素;而照相机移动方式对背景的构建没有要求,它可以支持非常复杂的背景。

是否可以无限延伸:

理论上四种方式都可以实现无限延伸。但是对于照相机移动方式来说,实现起来会比较麻烦。而另外三种方式都是循环利用同一个背景,所以天然支持无限延伸的背景。

复合多层背景的滚动,实现视差效果:

有了上面的工作,这一步也是顺其自然就可以完成:

我们首先要构建多层背景,多层背景可以同时只是用一种滚动方式;也可以不同层背景使用不同的滚动方式。比如最远的背景由于基本上都是简单的平铺可以使用 UV 滚动方式构建;而再近一点细节较多的背景层可以考虑后几种滚动方式构建背景。

之后是移动,我们需要根据每一层背景的距离决定其在移动过程中的移动速度。速度如何决定并没有统一的方法或是模式。总之,这一点听听美术们的意见是比较合适的。

其它问题

完成以上工作,我们的视差背景就算顺利建立起来了。不过,在实现中我们依旧会碰到一些其他的问题:

比如当开发者构建了一个有着复杂结构的背景,且使用了除去照相机移动方式以外的其他背景滚动方式实现了背景滚动。可能需要将背景取景区域覆盖的背景精灵左右都扩展一个背景精灵作为缓冲。以防止建立在背景精灵之上的背景布景(比如开发者在表示平原的背景精灵之上,放了另一个树的精灵作为此平原背景精灵的布景)在显示过程中出现突然出现和突然消失的情况。

或者在有些情况下,背景本身就会移动(比如云层背景)或者是背景之上存在一些独立移动的背景布景。这个时候我们也需要对上面的背景滚动方式进行相应的调整。

也可能是背景上需要支持某些特效,这种情况下我们的背景也可能需要特殊处理。

这里就不一一列举了。这里描述的是一些视差背景的基本细节,具体问题还需要在具体实现中留给开发者们自己去解决。

上一篇:@GetMapping@PostMapping@PutMapping@DeleteMapping@PatchMapping


下一篇:Mac使用安卓模拟器-网易MuMu