【Unity基础】Unity输入与Time.deltaTime的使用原则

Unity 输入为什么有时需要乘 Time.deltaTime,有时却不需要呢?

本文将从 mousePositionDelta 与 GetAxis 出发,理解输入数据、位移与时间的关系。

在 Unity 中处理用户输入时,我们经常看到两种不同的代码。

使用键盘控制物体移动:

csharp 复制代码
float horizontal = Input.GetAxis("Horizontal");

transform.Translate(
    Vector3.right * horizontal * speed * Time.deltaTime
);

使用鼠标控制摄像机旋转:

csharp 复制代码
float mouseX = Input.GetAxis("Mouse X");

transform.Rotate(
    Vector3.up * mouseX * sensitivity
);

第一段代码乘了 Time.deltaTime,第二段却没有。

而 Unity 还提供了另一个属性:

csharp 复制代码
Vector3 delta = Input.mousePositionDelta;

它同样表示鼠标的移动量,通常也不需要乘 Time.deltaTime。

这就产生了几个问题:

  • mousePositionDelta 是每一帧鼠标位置发生的变化吗?
  • GetAxis() 读取的不也是每一帧的输入吗?
  • 为什么一个需要乘 Time.deltaTime,另一个却不需要?
  • 有没有一条适用于所有游戏输入的通用规则?

要彻底理解这些问题,需要先区分三个概念:输入状态、运动速度与运动位移。

一、从最基础的问题开始:Time.deltaTime 到底有什么作用?

Time.deltaTime 表示上一帧到当前帧之间经过的时间,通常以秒为单位。

假设游戏以 50 FPS 运行:

Δ t = 1 50 = 0.02 秒 \Delta t=\frac{1}{50}=0.02\text{ 秒} Δt=501=0.02 秒

如果我们希望一个物体以每秒 5 个世界单位的速度向右移动,那么当前帧应该移动:

Δ x = v × Δ t \Delta x=v\times\Delta t Δx=v×Δt

代入数值:

Δ x = 5 × 0.02 = 0.1 \Delta x=5\times0.02=0.1 Δx=5×0.02=0.1

因此,在 Unity 中可以写成:

csharp 复制代码
float speed = 5f;

transform.Translate(
    Vector3.right * speed * Time.deltaTime
);

如果没有 Time.deltaTime,物体就会每帧移动 5 个单位。帧率越高,每秒移动次数越多,物体也就移动得越快。

Time.deltaTime 并不是用来让所有数值都与帧率无关,而是用来将"单位时间的变化率"转换为"当前时间步的变化量"。

已知量 乘以时间后
速度:米/秒 位移:米
角速度:度/秒 旋转角度:度
变化率:数值/秒 数值变化量

但如果某个值本身已经表示位移或角度变化,就没有必要再次乘时间。

二、mousePositionDelta:返回的已经是鼠标位移

Unity 的 Input.mousePositionDelta 是只读 Vector3 属性,表示当前输入更新中的鼠标相对位移,单位为像素,其中 z 分量始终为 0。

例如:

csharp 复制代码
Vector3 delta = Input.mousePositionDelta;

假设得到:

text 复制代码
delta = (20, -10, 0)

表示在这一输入更新期间,鼠标向右移动了 20 像素、向下移动了 10 像素。

这些值描述的是已经发生的移动距离,而不是每秒移动多少像素。

1. 为什么不需要乘 Time.deltaTime?

假设鼠标每水平移动 1 像素,就让摄像机旋转 0.5 度:

csharp 复制代码
float sensitivity = 0.5f;
float mouseX = Input.mousePositionDelta.x;

transform.Rotate(
    Vector3.up * mouseX * sensitivity
);

当鼠标移动 20 像素时:

Δ θ = 20 × 0.5 = 10 ∘ \Delta\theta=20\times0.5=10^\circ Δθ=20×0.5=10∘

旋转角度已经计算出来,不需要再次乘时间。

如果写成:

csharp 复制代码
transform.Rotate(
    Vector3.up * mouseX * sensitivity * Time.deltaTime
);

就会让同样的鼠标移动距离,在不同帧率下产生不同的累计旋转角度。

2. 为什么重复乘时间反而可能造成帧率依赖?

假设鼠标以相同的物理速度移动,持续 1 秒,总共产生 600 像素位移。

条件 30 FPS 60 FPS
平均每帧位移 20 px 10 px
直接累加的总位移 20 × 30 = 600 px 10 × 60 = 600 px
若每帧再乘 deltaTime,累计结果 20 × 1/30 × 30 = 20 10 × 1/60 × 60 = 10

理想化示例:假设鼠标位移均匀分布、输入无丢失;最后一行省略相同的灵敏度系数。

如果直接累加每帧鼠标位移,两个帧率下总量都是 600 像素。但如果每帧再乘一次 Time.deltaTime,累计结果就不同了。

原因在于鼠标位移本来已经随着采样时间变化:同样的移动速度下,时间间隔越长,单次采样得到的位移通常越大。再次乘时间,相当于重复引入时间因素。

结论:当鼠标位移直接用于计算旋转角度或拖拽增量时,通常不乘 Time.deltaTime。

三、GetAxis:同一个 API,可能返回两种不同含义的数据

最容易造成误解的地方是 Input.GetAxis()。

不能简单地说"GetAxis 需要乘 Time.deltaTime",也不能说"GetAxis 已经与帧率无关,所以不需要乘 Time.deltaTime"。

GetAxis() 是虚拟轴读取接口,返回值的含义取决于虚拟轴映射的输入设备及配置。

1. GetAxis("Horizontal"):返回方向控制量

csharp 复制代码
float horizontal = Input.GetAxis("Horizontal");

当 Horizontal 配置为键盘方向输入时,返回值通常在 -1 到 1 之间。

按键状态 典型目标轴值
按住 A -1
没有方向输入 0
按住 D 1

使用 GetAxis() 时,轴值可能受到 Sensitivity、Gravity 等参数影响,逐渐向目标值变化。但无论如何,它仍主要表示玩家希望向哪个方向移动,以及输入强度有多大。

它不表示物体在这一帧已经移动了多少世界单位。

因此,如果 horizontal = 1 表示向右全速移动,speed = 5 表示每秒移动 5 个单位:

Δ x = horizontal × speed × Δ t \Delta x=\text{horizontal}\times\text{speed}\times\Delta t Δx=horizontal×speed×Δt

csharp 复制代码
float horizontal = Input.GetAxis("Horizontal");
float speed = 5f;

transform.Translate(
    Vector3.right * horizontal * speed * Time.deltaTime
);

这里乘 Time.deltaTime,是因为 horizontal * speed 得到的是目标移动速度,而不是已经发生的位移。

2. GetAxis("Mouse X"):返回经过映射处理的鼠标移动量

csharp 复制代码
float mouseX = Input.GetAxis("Mouse X");

当虚拟轴配置为 Mouse Movement 时,Unity 返回的是当前鼠标位移经过该轴灵敏度处理后的数值。它不是一般意义上的 -1 到 1 方向强度,也不一定限制在这个范围内。

csharp 复制代码
float mouseX = Input.GetAxis("Mouse X");

transform.Rotate(
    Vector3.up * mouseX * sensitivity
);

这里的 mouseX 表示已经发生的鼠标移动所对应的控制量。如果直接将它映射为角度变化,就不需要再乘 Time.deltaTime。

Unity 官方 GetAxis() 文档也分别演示了两类做法:使用 Horizontal / Vertical 控制按速度移动时乘 Time.deltaTime;使用 Mouse X / Mouse Y 控制鼠标视角时不乘。

四、为什么 mousePositionDelta 与 GetAxis("Mouse X") 都不需要乘时间?

csharp 复制代码
float a = Input.mousePositionDelta.x;
float b = Input.GetAxis("Mouse X");

两者都可以反映鼠标相对移动,但并不等价。

比较维度 mousePositionDelta.x GetAxis("Mouse X")
数据来源 鼠标相对位移 Input Manager 虚拟轴
数据含义 像素位移 经过虚拟轴配置处理的移动量
单位 像素 映射后的轴值
是否受轴 Sensitivity 影响 否 是
直接用于旋转时是否乘 deltaTime 通常不乘 通常不乘
是否需要配置虚拟轴 不需要 需要

例如,使用 mousePositionDelta 实现第一人称视角:

csharp 复制代码
float mouseX = Input.mousePositionDelta.x;
float mouseY = Input.mousePositionDelta.y;
float sensitivity = 0.1f; // 度/像素

transform.Rotate(
    -mouseY * sensitivity,
    mouseX * sensitivity,
    0f
);

使用鼠标虚拟轴:

csharp 复制代码
float mouseX = Input.GetAxis("Mouse X");
float mouseY = Input.GetAxis("Mouse Y");
float sensitivity = 2f; // 针对映射后轴值的系数

transform.Rotate(
    -mouseY * sensitivity,
    mouseX * sensitivity,
    0f
);

两段代码不能简单地共用同一个灵敏度数值,因为它们的输入量纲和映射方式不同。

五、为什么 GetAxis 与帧率无关,却仍然要乘 deltaTime?

Unity 文档说明 GetAxis() 的返回值不依赖于帧率。这很容易被误解为既然输入值已经与帧率无关,为什么还要乘时间?

实际上,输入值的帧率独立性,与游戏行为的帧率独立性,是两个不同问题。

csharp 复制代码
float horizontal = Input.GetAxis("Horizontal");

不管游戏运行在 30 FPS 还是 120 FPS,玩家持续向右输入时,这个虚拟轴都可以达到接近 1 的值。这是输入状态的含义。

但如果:

csharp 复制代码
transform.position += Vector3.right * horizontal * 5f;

即使 horizontal 是稳定的 1,代码每帧都让物体移动 5 个单位。

  • 30 FPS 时,每秒移动约 150 个单位。
  • 120 FPS 时,每秒移动约 600 个单位。

问题出在我们怎样使用输入值,而不是输入值本身。

改成:

csharp 复制代码
transform.position +=
    Vector3.right * horizontal * 5f * Time.deltaTime;

才把连续移动转换成按经过时间累积的运动。

反过来,mousePositionDelta 本身就是位移,即使每帧数值不同,累加后的结果仍可以对应相同的鼠标物理运动。

帧率独立性不意味着所有数值都应该乘或不乘 Time.deltaTime。必须分析整个计算过程。

六、一个重要例外:并不是只有"位移"才不需要乘时间

例如:

csharp 复制代码
void FixedUpdate()
{
    float horizontal = Input.GetAxis("Horizontal");
    rb.AddForce(Vector3.right * horizontal * force);
}

这里 horizontal 仍然是方向控制量,但没有乘 Time.fixedDeltaTime。

原因是 Rigidbody.AddForce() 是物理系统接口。施加力、施加冲量与直接修改物体位移是不同操作;Unity 会根据力模式和物理时间步处理相应变化。

如果使用默认 ForceMode.Force,通常应提供期望的力,而不是先手动乘时间。

实践补充:对于物理运动,通常应在 Update() 中采集并缓存输入意图,在 FixedUpdate() 中调用 AddForce()。上面的代码仅用于对比时间处理机制。

同样,直接设置 Unity 6 的 Rigidbody.linearVelocity 时,需要传入速度,而不是预先计算每帧位移。

因此,更准确的判断需要同时考虑:

  1. 输入数据表示什么?
  2. 后续运算将它转换成什么?
  3. 最终调用的 API 需要什么单位的数据?
  4. 是否已经有其他系统负责处理时间?

七、通用结论:Time.deltaTime 的使用由量纲和计算目标决定

可按以下步骤判断:

  1. 识别输入值: 它是方向、强度、位置、位移,还是速度?
  2. 明确目标计算: 想得到每帧位移、旋转角度、速度、力,还是目标位置?
  3. 检查目标 API 的语义: 例如 Translate、Rotate、AddForce、linearVelocity。
  4. 决定是否乘时间: 如果要将速度或角速度转换为当前时间步的位移或角度,乘以对应时间间隔;如果已是变化量,或时间由 API / 物理系统处理,就不要重复乘。

最可靠的方法之一是检查量纲:

米 秒 × 秒 = 米 \frac{\text{米}}{\text{秒}}\times\text{秒}=\text{米} 秒米×秒=米

度 秒 × 秒 = 度 \frac{\text{度}}{\text{秒}}\times\text{秒}=\text{度} 秒度×秒=度

鼠标位移直接映射为旋转角度时:

像素 × 度 像素 = 度 \text{像素}\times\frac{\text{度}}{\text{像素}}=\text{度} 像素×像素度=度

如果再乘秒:

像素 × 度 像素 × 秒 = 度 ⋅ 秒 \text{像素}\times\frac{\text{度}}{\text{像素}}\times\text{秒}=\text{度}\cdot\text{秒} 像素×像素度×秒=度⋅秒

就不再是期望的角度单位。

量纲检查不能代替对 API 行为的理解,但能够帮助我们发现错误。

常见输入的判断总结

使用场景 数据含义 通常乘 deltaTime?
GetAxis("Horizontal") 控制移动 方向强度 × 移动速度 是
GetAxisRaw("Vertical") 控制移动 方向强度 × 移动速度 是
GetAxis("Horizontal") 控制持续旋转 方向强度 × 角速度 是
GetAxis("Mouse X") 控制视角 映射后的鼠标位移 × 角度系数 否
mousePositionDelta 控制视角 像素位移 × 度/像素 否
mousePosition 定位对象 屏幕绝对位置 否
GetButtonDown() 触发跳跃 一次性事件 否
GetAxis("Horizontal") 控制施加力 输入强度 × 力 通常否,由物理 API 处理

以上是针对典型用法的判断,不是对 API 的永久限制。例如,如果先把鼠标位移转换为速度,再利用它实现按时间变化的行为,公式也要相应调整。

八、结语:不要记住 API 是否乘时间,而要理解数据如何参与计算

通过 mousePositionDelta 与 GetAxis() 的比较,可以得到三层结论。

第一层:具体 API 的使用规律。

鼠标的相对位移直接用于旋转时,通常不需要乘 Time.deltaTime。键盘方向输入用于按速度移动时,通常需要乘 Time.deltaTime。

第二层:输入系统的设计规律。

输入系统提供的数值可能表示状态、控制强度、绝对位置或变化量。同一个输入接口也可能因设备和配置不同而返回不同语义的数据。

第三层:通用编程原则。

不要根据 API 名称决定是否乘以时间,而要根据数据的物理含义、单位以及目标操作来决定。只有在需要将单位时间的变化率转换成当前时间步变化量时,才应该引入对应的时间间隔。

这不只是 Unity 输入处理的经验,也是理解游戏运动、动画、物理仿真以及实时交互程序的一条基础原则。


参考资料

相关推荐
chen_22711 小时前
godot做仪表hmi
游戏引擎·godot
郝学胜-神的一滴17 小时前
AI 编程智能体 22:解析器赋能与实战落地全指南
人工智能·程序人生·游戏·unity·游戏引擎
BuHuaX21 小时前
Lua 与 ToLua:运行机制及热更新原理
开发语言·unity·junit·c#·lua
Solange__2 天前
在线3D预览器、Blender和游戏引擎怎么选?按5类任务比较格式、隐私与编辑性
人工智能·游戏·3d·游戏引擎·blender·1024程序员节·关卡设计
peter67682 天前
unity2d游戏开发经验总结
unity
郝学胜-神的一滴2 天前
游戏设计:原型与开发 19:U3D工位布置与性格说明书
人工智能·算法·unity·unreal engine·游戏策划·游戏设计
Aman19842 天前
Claude Code做Unity项目移植(二): 自动执行只读操作,告别频繁的
unity·游戏开发·vibe coding·claude code
暴走约伯3 天前
【VFX工具链源码拆解 02】告别机械劳动(上):一键统改百个子粒子颜色与批量重命名
3d·unity·c#·材质
郝学胜-神的一滴3 天前
游戏设计:原型与开发 18:Unity入门的第一课
人工智能·程序人生·游戏·ui·unity·游戏引擎