物理调优
大多数“糟糕的物理”不是引擎中的错误——而是固定时间步长模拟与可变速率渲染循环之间的不匹配,或者质量/阻力/CCD/层设置未调优。本技能涵盖使物理稳定且响应灵敏的与引擎无关的调节项;将其与 godot-physics 或 unity-physics 搭配使用,以获取具体 API。
何时使用
- 当运动抖动、物体穿墙(穿模)、堆叠爆炸,或移动感觉漂浮/粘滞/卡顿时使用。
- 用于决定哪些内容放在固定(物理)步长中,哪些放在渲染帧中,以及如何在它们之间插值。
- 用于调优重力、质量、阻力、恢复系数、求解器迭代、休眠,以及碰撞层/遮罩。
**何时*不*使用:** 对于引擎的具体物理节点/组件和碰撞回调,请使用 godot-physics 或 unity-physics。对于*移动决策*(何时跳跃、AI 转向),请使用 input-systems 和 game-ai。对于平台跳跃跳跃手感细节,例如土狼时间/跳跃缓冲,这属于输入/控制器领域——参见 input-systems 和 platformer 类型。
核心工作流
- 以固定时间步长运行物理。 以恒定速率模拟(例如 50–60 Hz)。固定
dt使模拟大致确定且稳定;可变dt会使积分和碰撞不一致。 - 将物理工作放在物理回调中,而不是渲染帧中。在固定步长(
FixedUpdate/_physics_process)中应用力/速度并读取碰撞,使用该步长的dt。 - 在物理 tick 之间插值渲染。 渲染帧率 ≠ 物理速率,因此应将变换平滑插值到最新物理状态,或启用引擎的 Rigidbody 插值,以消除可见卡顿。
- 调优刚体,而不是场景。 设置质量以获得相对重量,设置阻力以获得阻尼,设置每个对象的重力缩放,并通过材质设置恢复系数/摩擦。
- 使用 CCD 阻止穿模,针对小/快刚体;限制最大速度。
- 稳定堆叠/关节,通过更多求解器迭代、合理的质量比,以及为静止刚体启用休眠。
- 通过手感和压力测试验证。 以低和高帧率游玩;向薄墙投掷快速物体;堆叠并推挤刚体。报告你观察到的现象。
模式
1. 固定时间步长用于模拟,渲染插值用于平滑
# Physics callback: runs at the FIXED rate. Use its dt for all integration.
func _physics_process(dt): # Unity: void FixedUpdate()
velocity += gravity * dt # integrate with the FIXED dt
move_and_slide() # engine resolves collisions this step
_prev_pos = _curr_pos; _curr_pos = global_position # record for interpolation
# Render frame: runs as fast as the display. Interpolate between physics states.
func _process(_frame_dt): # Unity: void Update()
var alpha = Engine.get_physics_interpolation_fraction() # 0..1 within the tick
visual.global_position = _prev_pos.lerp(_curr_pos, alpha)
# RIGHT: integrate in the fixed step, render via interpolation.
# WRONG: applying forces in _process/Update with frame dt — speed and collisions
# then depend on frame rate and jitter under load.
大多数引擎已为你提供此功能(Godot physics_interpolation/Rigidbody 插值;Unity Rigidbody.interpolation = Interpolate)。优先使用内置功能,而不是手写实现。
2. 阻止穿模:CCD + 速度上限
# Fast, small bodies skip past thin colliders between ticks. Two fixes:
body.continuous_cd = true # RigidBody3D bool (RigidBody2D: CCD_MODE_* enum). Unity: rb.collisionDetectionMode = Continuous
# Cap velocity so a single step can't move more than ~one collider thickness.
const MAX_SPEED := 40.0
if velocity.length() > MAX_SPEED:
velocity = velocity.normalized() * MAX_SPEED
# Rule of thumb: max_distance_per_step (= speed / physics_hz) should be < the
# thinnest wall. Raise physics_hz or enable CCD when that fails.
3. 刚体调优:质量、阻力、重力缩放、材质
# Mass is RELATIVE weight in collisions; it does NOT change fall speed (gravity
# accelerates all masses equally). Use drag and gravity_scale to shape feel.
body.mass = 2.0 # heavier pushes lighter in collisions
body.linear_damp = 0.5 # air drag: higher = stops sooner (Unity: drag)
body.gravity_scale = 1.5 # per-object gravity multiplier (snappier fall)
# Bounce/slide come from the physics material, not code:
material.bounce = 0.2 # restitution 0..1 (Unity: bounciness)
material.friction = 0.8 # surface grip
4. 碰撞层与遮罩(谁与谁碰撞)
# A body is ON its layer(s) and SCANS the layers in its mask. Both directions of a
# pair must be configured for them to interact.
player.collision_layer = LAYER_PLAYER
player.collision_mask = LAYER_WORLD | LAYER_ENEMY # player detects world+enemies
pickup.collision_layer = LAYER_PICKUP
pickup.collision_mask = LAYER_PLAYER # pickup only reacts to player
# Unity equivalent: assign GameObject layers and edit the Physics collision matrix
# (or Physics.IgnoreLayerCollision). Keep a named layer constant table, not magic numbers.
陷阱
- 在渲染帧中应用力/移动(
Update/_process)会使行为依赖帧率——更快的 PC 运行更快,碰撞变得不稳定。在固定步长中进行模拟。 - 可见抖动即使使用固定步长,通常也意味着没有渲染插值:物理速率和显示速率彼此冲突。启用插值。
- 穿模穿过薄墙:离散碰撞会错过快速移动者。启用 CCD,限制速度,加厚墙壁,或提高物理速率。
- 期望更重的物体下落更快。 重力是加速度;质量影响碰撞响应,而不是下落速度。使用
gravity_scale/阻力来获得手感。 - 堆叠爆炸 / 关节抖动:质量比过于极端,或求解器迭代次数太少。保持质量比适度,并提高迭代次数。
- 永不停止的刚体会消耗 CPU 并抽搐。为静止物体启用休眠和合理的休眠阈值。
- 单向层设置:A 的遮罩包含 B,但 B 的遮罩不包含 A。检测/碰撞可能需要双方;验证完整矩阵。
- 巨大的
dt尖峰(加载卡顿、断点)会破坏积分。限制最大物理步长/子步计数,以免停顿导致一切被弹飞。
参考资料
references/timestep-and-ccd.md— 固定时间步长累加器循环、插值数学、子步进、CCD 模式、求解器/迭代调优、休眠,以及稳定性检查清单。
相关技能
godot-physics,unity-physics— 具体刚体、碰撞体和回调。input-systems— 响应式控制、跳跃缓冲、土狼时间。game-ai— 必须与物理步长一致的代理移动。platformer,fps-shooter— 手感依赖此调优的类型。