2D 视差

前言

视差是一种视觉效果,通过让图像纹理相对于相机以不同速度移动来模拟纵深。Godot 提供了 Parallax2D 节点来实现这种效果。这方面的坑还是不少的,因此本页面会对部分属性进行深入描述,并会介绍纠正一些常见错误的方法。

备注

本页面介绍如何使用 Parallax2D 节点。建议使用它来替代 ParallaxLayerParallaxBackground 节点。

入门

视差节点支持添加将对象渲染为子节点的节点,因此你可以使用一个或多个节点来组成每个图层。首先,将每个你想要独立滚动的节点(或多个节点)放置为它们各自视差节点的子节点。确保使用的纹理的左上角位于 (0, 0) 交叉点,如下图所示。请参阅 定位 部分了解这一节点为何重要。

../../_images/2d_parallax_size_viewport.webp

上面的场景在 Sprite2D 中使用了一张准备好的纹理来表现较高的云层,但你也可以轻松地使用多个间隔开的节点来组成该层。

滚动缩放

视差效果的核心是 scroll_scale 属性,该属性为滚动速度系数,能够让图层的移动速度和相机的移动速度产生差别,不同的轴可以分别进行调整。设为 1 时视差节点与相机同速。滚动时,如果你想要让图像看起来比较远,请使用小于 1 的值,设为 0 就会完全停止。如果你想要让图像看起来离相机很近,请使用大于 1 的值,这样图像滚动得就会更快。

上面这个场景由五个图层构成,对应的 scroll_scale 可以设置为:

  • (0.7, 1) - 森林

  • (0.5, 1) - 山丘

  • (0.3, 1) - 低处的云

  • (0.2, 1) - 高处的云

  • (0.1, 1) - 天空

下面的这个视频展示的就是这些值在游戏中的对滚动的影响:

无限重复

Parallax2D 还可以顺便让纹理产生无限重复的假象。相机移动时,repeat_size 可以让节点的位置根据这个值向前或向后吸附。这个效果的原理是让所有子级画布项重复一次并使用这个值进行偏移。而相机在原本的图像和重复的图像之间滚动,悄悄跳回原本的位置就会造成图像循环的效果。

../../_images/2d_parallax_scroll.gif

这是一个很巧妙的效果,不熟悉的用户很容易在设置时出错。那我们就来看几个常见问题的“原理”和“解法”。

大小问题

为了方便实现无限重复效果,图像本身最好就需要能够无缝重复,并且需要图像的大小在设置 repeat_size 之前至少要和视口大小一致。如果无法获取合适的资产素材,也有一些方法可以事先处理一下图像的大小。

下例是一个在视口中显得太小的纹理:

../../_images/2d_parallax_size_bad.webp

我们可以看到视口的尺寸是 500x300 而纹理的尺寸是 288x208。如果我们把 repeat_size 设置为图像的尺寸,无限重复效果在滚动时就会出问题,因为原始纹理没有覆盖整个视口。如果我们把 repeat_size 设置为视口的尺寸,那么就会有一个很大的间隙。那该怎么办呢?

把视口调小

最简单的答案是将视口设置为与纹理大小相同或更小。在 项目设置 > 显示 > 窗口 中,更改 视口宽度视口高度 设置,使其与背景匹配。

../../_images/2d_parallax_size_viewport.webp

缩放 Parallax2D

如果你想要实现的不是完美像素风,或者不介意存在略微的模糊,那么你可以选择将纹理放大到适合屏幕的尺寸。请设置 Parallax2Dscale,所有子级纹理都会对应发生缩放。

缩放子节点

和放大 Parallax2D 类似的是将 Sprite2D 节点放大到能够覆盖住屏幕。请注意,Parallax2D.repeat_sizeSprite2D.region_rect 等设置并不会考虑缩放,因此这些值也需要根据缩放进行调整。

../../_images/2d_parallax_size_scale.webp

重复纹理

你也可以通过提前准备子节点来确保从一开始就走上正确的道路。如果你有一个希望重复的 Sprite2D,但它的尺寸太小,就可以按照以下步骤来重复它:

下图中可以看到,把图像重复一次就足够覆盖住屏幕了。

../../_images/2d_parallax_size_repeat.webp

位置问题

用户经常错误地把所有纹理都设成在 (0,0) 居中:

../../_images/2d_parallax_single_centered.webp

这会给无限重复效果带来问题,应当尽量避免。“无限重复画布”从 (0,0) 开始,向右下扩展至 repeat_size 的大小值。

../../_images/2d_parallax_single_expand.webp

如果纹理是以 (0,0) 交叉点为中心的,那么无限重复画布只会被部分覆盖,因此也只会部分地重复。

repeat_times 调大有用吗?

从技术上来说,增加 repeat_times 在某些情况下将是可行的,但这是一种暴力的解决方案,而不是它被设计用于解决的问题(我们稍后会讨论这个问题)。更好的解决方法是理解重复效果的工作原理,并在开始时就适当地设置视差纹理。

首先,检查是否有任何纹理溢出到画布的负轴向。确保在视差节点中使用的纹理都位于从 (0,0) 开始的“无限重复画布”内。这样,如果正确设置了 Parallax2D.repeat_size,效果应该看起来像这样,图像的单次循环与视口大小相同或更大:

../../_images/2d_parallax_repeat_good_norect.webp

想象一下图像是如何在屏幕上滚动的,它首先显示红色矩形内的内容(由 repeat_size 确定),当到达黄色矩形内时,它会将图像向前拉动,从而给人一种永远滚动的感觉。

../../_images/2d_parallax_repeat_good.webp

如果你将图像放置在“无限重复画布”之外的位置,当摄像机到达黄色矩形区域时,图像的一半会被裁切掉,然后才会向前跳转,就像下图所示:

../../_images/2d_parallax_repeat_bad.webp

滚动偏移

如果你的视差纹理已经正常工作,但你希望它从不同的点开始,Parallax2D 附带一个 scroll_offset 属性,用于偏移无限重复画布的开始位置。例如,如果你的图像是 288x208,将 scroll_offset 设置为 (-144,0)(144,0) 可使其从图像的中间位置开始。

重复次数

理想情况下,按照本指南操作,你的视差纹理足够大,即使在缩小时也能覆盖屏幕。到目前为止,我们在 288x208 视口内拥有一个完美贴合的 288x208 纹理。但是,当我们通过将 Camera2D.zoom 设置为 (0.5, 0.5) 进行缩小时,会出现问题:

../../_images/2d_parallax_zoom_single.webp

尽管在默认缩放级别下,视口的所有内容都已正确设置,但缩小后会使它小于该视口,从而破坏无限重复效果。这时 repeat_times 就可以提供帮助。将值设置为 3(前后各一个额外的重复),现在它就足够大,可以容纳无限重复的效果了。

../../_images/2d_parallax_zoom_repeat_times.webp

如果这些纹理需要垂直地重复,我们应该为 repeat_size 指定一个 y 值。repeat_times 也会自动在上方和下方添加重复。这只是一个水平视差,因此它会在图像上方和下方留下一个空白块。我们如何解决这个问题?我们需要发挥创造力!在这个例子中,我们将天空拉高,将草精灵拉低。纹理现在支持正常缩放级别以及缩小到一半大小。

../../_images/2d_parallax_zoom_repeat_adjusted.webp

分屏

大多数用 Godot 制作分屏游戏的教程一开始都是要写一个简单的脚本,把第一个 SubViewport 的 Viewport.world_2d 赋值给第二个 SubViewport,从而实现屏幕的共享。此时就会产生如何在这两个屏幕之间共享视差效果的问题。

视差效果模拟透视的方法是让不同的纹理根据其与相机的关系移动不同的距离。存在多个相机时,很显然就会出现问题,因为同一个纹理不可能同时出现在两个不同的地方!

解决方法也是有的,把视差节点往第二个(或者第三第四个) SubViewport 里复制一份就好了。在双人游戏里是这个样子的:

../../_images/2d_parallax_splitscreen.webp

当然,现在两个背景都会在两个 SubViewport 中显示。我们希望的是每个视差背景只在其对应的视口中显示。你可以通过以下方式实现这一点:

  • 将所有视差节点的 visibility_layer 保留为其默认值 1 。

  • 将第一个 SubViewport 的 canvas_cull_mask 设置为仅显示层 1 和 2。

  • 对第二个 SubViewport 执行相同的操作,但使用图层 1 和 图层 3。

  • 为第一个 SubViewport 中的视差节点提供一个共同的父节点,并将其 visibility_layer 设置为 2。

  • 对第二个 SubViewport 的视差节点执行相同的操作,但使用 3 号图层。

这是如何工作的?如果一个画布项的 visibility_layer 与 SubViewport 的 canvas_cull_mask 不匹配,它将隐藏所有子节点,即使它们匹配也是如此。我们利用这一点,让 SubViewport 停止那些父节点没有匹配的 visibility_layer 的视差节点的渲染。

在编辑器中预览

4.3 版本之前推荐的是把每个层都放到各自的 ParallaxBackground 下面,然后启用 follow_viewport_enabled 属性,再对各个层进行缩放。这个方法要用对还挺难的,用 CanvasLayer 代替 ParallaxBackground 也能达到想要的效果。

备注

另外推荐 KoBeWi 的“Parallax2D Preview”插件。这个插件提供了很多预览模式,挺好用的!