Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

使用物理插值

如何在 Godot 游戏中加入物理插值?有什么注意事项吗?

我们已尽可能使系统易于使用,许多现有游戏只需些许修改即可运行。尽管如此,有些情况仍需要特殊处理,这些将在后文中详细说明。

启用物理插值设置

第一步是在项目设置 > 物理 > 通用 > 物理插值中启用物理插值。现在你可以运行游戏了。

这很可能看起来没有什么大的不同,特别是当你以 60 TPS 或其倍数运行物理引擎时。然而,在幕后发生了相当多的事情。

小技巧

要将现有游戏转换为使用插值功能,强烈建议你暂时将项目设置 > 物理 > 通用 > 每秒物理周期数设置为较低的值(例如 10),这会使插值问题更加明显。

将(几乎)所有游戏逻辑从 _process 移到 _physics_process

物理插值最基本的要求(你可能已经在做了)是,你应该在 _physics_process(在物理周期运行)而不是 _process(在渲染帧运行)中移动物体并执行游戏逻辑。这意味着你的脚本通常应该在 _physics_process 中进行大部分处理,包括响应输入和 AI。

仅在物理帧内设置对象的变换可以让自动插值处理物理帧之间的变换,并确保游戏在任何机器上运行的效果都相同。额外的好处是,如果游戏以高 FPS 渲染,这还能减少 CPU 使用率,因为(例如)AI 逻辑将不再在每一渲染帧上运行。

备注

如果你尝试在物理帧之外设置插值对象的变换,插值位置的计算将会出错,导致画面出现抖动。这种抖动可能不会在你的设备上显现,但一定会在某些玩家的设备上发生。因此,应该避免在物理帧之外设置插值对象的变换。Godot 如果检测到这种情况,会尝试在编辑器中发出警告。

小技巧

这只是一条软性规则。在某些情况下,你可能希望在物理帧之外传送对象(例如在开始关卡或重生对象时)。不过,通常情况下,你应该在物理帧中应用变换。

确保所有间接移动都在物理周期中进行

请注意,在 Godot 中,节点不仅可以在你自己的脚本中直接移动,还可以通过某些自动的方式(如补间动画、动画和导航)进行移动。如果你使用这些方法来移动对象(这些方法也可以用于控制不进行插值的属性),那么所有这些方法的运行时间点都应设置为在物理周期期间而非每帧(“idle”)中。

备注

另外需要注意的是,不仅可以通过移动节点自身来改变位置,还可以通过移动场景树中的父节点来移动。因此,父节点的移动也应该仅在物理周期期间进行。

选择物理周期率

使用物理插值时,渲染与物理解耦,你可以根据自己的游戏选择合适的值。你不再受限于用户显示器刷新率的倍数(才能实现无卡顿的游戏体验,前提是达到目标 FPS)。

大致的参考:

低周期率(10-30)

中等周期率(30-60)

高周期率(60+)

CPU 性能更佳

复杂场景中物理行为良好

适合快速物理

引入一些输入延迟

适合第一人称游戏

适合赛车游戏

简单物理行为

备注

你可以在开发过程中随时更改周期速率,只需修改项目设置即可。

传送对象时调用 reset_physics_interpolation()

大多数情况下,在两次物理帧之间进行插值是你想要的效果。然而,有一种情况可能并不适合使用插值。那就是当你最初放置物体,或者将物体移动到新位置时。在这种情况下,你并不希望在物体原来的位置(例如原点)和初始位置之间进行平滑移动——你想要的是瞬间移动。

解决这个问题的方法是调用 Node.reset_physics_interpolation 函数。这个函数在底层的作用是将对象内部存储的上一帧变换设置为与当前变换相等。这确保了在这两个相等的变换之间进行插值时,不会产生任何移动。

即使你忘记调用这个方法,在大多数情况下通常也不会出现问题(尤其是在高周期率下)。你可以很容易地将这个问题留到游戏打磨阶段再处理。最坏的情况就是在移动它们时可能会看到一帧左右的拖影效果——当你需要时自然会知道!

实际上有两种使用 reset_physics_interpolation() 的方法:

立定起步(例如玩家)

  1. 设置初始变换

  2. 调用 reset_physics_interpolation()

上一个变换和当前变换相同,因此不会产生初始移动。

移动起步(例如子弹)

  1. 设置初始变换

  2. 调用 reset_physics_interpolation()

  3. 立即设置变换为运动一个周期后的预期变换

前一个变换将作为起始位置,而当前变换将表现得好像已经完成了一次物理周期。这将立即开始移动对象,而不是有一个周期的延迟静止不动。

重要

请确保你按照上面所示的正确顺序设置变换并调用 reset_physics_interpolation(),否则你可能会看到不希望的“拖影”现象。

测试和调试贴士

即使你打算以 60 TPS 的物理帧率运行游戏,为了彻底测试你的插值并实现最流畅的游戏体验,强烈建议暂时将物理帧率设置为较低的值,例如 10 TPS。

此时游戏玩法可能无法完美运行,但你应该能更容易看到在哪些情况下应该调用 Node.reset_physics_interpolation,或者在哪些情况下你应该在例如 Camera3D 上使用自己的自定义插值。一旦你修复了这些情况,就可以将物理周期速率设置回所需的值。

以较低周期速率进行测试的另一个巨大优势是,你通常可以注意到其他与物理周期同步的游戏系统,并发现可能想要解决的故障。典型的例子包括设置动画混合值,你可能会决定在 _process() 中设置并手动进行插值。

备注

在 2D 中,通过调试 > 显示碰撞形状选项显示的可见碰撞形状的位置将会考虑物理插值。

相比之下,在 3D 环境中,可见碰撞形状的位置不会考虑物理插值。这意味着当物体移动时,可见碰撞形状的移动可能并不平滑,并且会略微出现在物体视觉呈现的前方。这并非程序错误,而是 3D 环境中物理插值实现方式所导致的。