一、FixedUpdate的定义与基本概念
FixedUpdate是Unity引擎中MonoBehaviour类提供的一个特殊回调方法,与普通的Update方法不同,它主要设计用于处理物理相关的计算和更新。
void FixedUpdate()
{
// 适合放置物理计算相关代码
rb.AddForce(Vector3.forward * 10f);
}
核心特性
- 固定时间间隔:FixedUpdate以固定的时间步长执行,不受帧率波动影响
- 物理系统同步:与Unity的物理引擎计算同步,在物理步骤前调用
- 可配置:执行频率可通过项目设置中的Fixed Timestep参数调整
二、内部调用机制
1. 物理计算循环
Unity内部的物理更新循环与常规游戏循环分离,遵循以下流程:
主循环每帧
└── 检查是否需要进行物理更新
├── 计算积累的时间
├── 如果积累时间 >= fixedTimeStep
│ ├── 调用所有启用的FixedUpdate()方法
│ ├── 进行物理模拟步骤
│ ├── 更新物理状态(碰撞器、刚体等)
│ ├── 生成碰撞/触发事件
│ ├── 减少积累时间(积累时间 -= fixedTimeStep)
│ └── 如果仍有足够的积累时间,重复上述步骤
└── 继续主循环其他操作
2. 积累时间机制
Unity使用时间积累器(accumulator)来确定何时执行FixedUpdate:
// Unity内部伪代码
float physicsTimeAccumulator = 0;
void MainGameLoop()
{
float deltaTime = CalculateDeltaTime();
physicsTimeAccumulator += deltaTime;
// 可能在一帧内执行多次FixedUpdate
while (physicsTimeAccumulator >= Time.fixedDeltaTime)
{
ProcessFixedUpdate();
SimulatePhysics();
physicsTimeAccumulator -= Time.fixedDeltaTime;
}
// 继续处理Update等其他方法
ProcessUpdate();
// ...
}
3. 时间步长设置
固定时间步长在Unity编辑器中通过:Edit → Project Settings → Time → Fixed Timestep配置。
默认值为0.02秒,相当于50Hz的物理更新频率。
Fixed Timestep = 0.02 → 每秒执行50次FixedUpdate和物理计算
Fixed Timestep = 0.01 → 每秒执行100次FixedUpdate和物理计算
三、FixedUpdate与Update的关键区别
特性 | FixedUpdate | Update |
调用频率 | 固定频率,与Time.fixedDeltaTime绑定 | 每帧一次,频率不固定 |
时间间隔 | 固定的(默认0.02秒) | 可变的,取决于帧率 |
主要用途 | 物理计算、力的应用 | 一般游戏逻辑、输入检测 |
执行时机 | 物理步骤之前 | 每帧渲染之前 |
多次调用 | 可能在一帧内执行多次或一帧内不执行 | 保证每帧执行一次 |
时间测量 | 使用Time.fixedDeltaTime | 使用Time.deltaTime |
执行频率可视化
当游戏运行在不同帧率下时,FixedUpdate和Update的调用关系:
高帧率(如120FPS):
Update: | | | | | | | | | | | | | (每帧调用一次)
FixedUpdate: | | | | | | | (按固定频率调用)
低帧率(如25FPS):
Update: | | | | | | (每帧调用一次)
FixedUpdate: | || | || | || | || | || | (一帧内可能多次调用)
四、物理系统与FixedUpdate
1. 物理操作同步
FixedUpdate与物理系统紧密集成,是进行以下操作的理想位置:
void FixedUpdate()
{
// 添加力
rigidbody.AddForce(moveDirection * moveSpeed);
// 修改刚体速度
rigidbody.velocity = new Vector3(x, y, z);
// 角速度处理
rigidbody.angularVelocity = Vector3.up * rotationSpeed;
// 射线检测碰撞 (物理相关的)
Physics.Raycast(transform.position, transform.forward, out hit);
}
2. 碰撞检测与FixedUpdate的关系
碰撞和触发器事件(OnCollisionXXX和OnTriggerXXX)由物理引擎触发,与FixedUpdate执行在相同的时间步长内:
1. FixedUpdate被调用
2. 物理引擎运行模拟
3. 碰撞被检测
4. 触发OnCollisionEnter/OnTriggerEnter等事件
5. 下一个FixedUpdate开始前,可能触发OnCollisionStay/OnTriggerStay
五、实际应用与最佳实践
1. 适合使用FixedUpdate的场景
// 玩家基于物理的移动控制器
void FixedUpdate()
{
// 获取输入
float moveHorizontal = Input.GetAxis("Horizontal");
float moveVertical = Input.GetAxis("Vertical");
// 创建移动向量
Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
// 应用力来移动玩家
rb.AddForce(movement * speed);
}
// 敌人AI的物理追踪行为
void FixedUpdate()
{
Vector3 directionToTarget = (target.position - transform.position).normalized;
rb.MovePosition(transform.position + directionToTarget * speed * Time.fixedDeltaTime);
}
2. 不适合使用FixedUpdate的场景
// 错误示例:在FixedUpdate中处理输入
void FixedUpdate()
{
// 不推荐:输入可能丢失
if (Input.GetButtonDown("Jump"))
{
Jump();
}
}
// 正确做法:输入检测放在Update中
void Update()
{
// 检测输入
if (Input.GetButtonDown("Jump"))
{
wantsToJump = true;
}
}
void FixedUpdate()
{
// 执行物理操作
if (wantsToJump)
{
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
wantsToJump = false;
}
}
3. 设置合理的Fixed Timestep
物理更新频率影响模拟精度和性能:
较小的Fixed Timestep (0.01秒):
✓ 物理模拟更精确,碰撞检测更准确
✓ 快速移动物体的碰撞更可靠
✗ CPU使用率增加
✗ 在低性能设备上可能导致性能问题
较大的Fixed Timestep (0.03-0.05秒):
✓ 降低CPU负载
✓ 适合物理需求较低的游戏
✗ 可能导致物理不稳定或"隧道效应"
✗ 快速移动物体可能错过碰撞
4. FixedUpdate的使用示例
以下是一个简单的代码示例,展示如何在FixedUpdate中通过力来移动一个刚体:
using UnityEngine;
public class PlayerController : MonoBehaviour
{
private Rigidbody rb;
public float speed = 5.0f;
void Start()
{
rb = GetComponent<Rigidbody>();
}
void FixedUpdate()
{
float moveHorizontal = Input.GetAxis("Horizontal");
float moveVertical = Input.GetAxis("Vertical");
Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical).normalized;
rb.AddForce(movement * speed); // 在FixedUpdate中应用力
}
}
在这个例子中:
- 玩家通过输入控制刚体的移动。
- AddForce方法在FixedUpdate中调用,因为力的应用属于物理逻辑,与物理引擎的更新步长保持一致。
一些常见项目类型的推荐设置:
- 精确物理模拟游戏:0.01秒 (100Hz)
- 一般3D游戏:0.02秒 (50Hz)
- 简单物理的2D游戏:0.025秒 (40Hz)
- 移动平台低性能游戏:0.03-0.05秒 (20-33Hz)
注意事项
在使用FixedUpdate时,需要注意以下几点:
- 避免非物理逻辑:不要在FixedUpdate中处理UI更新、动画控制等非物理相关逻辑,这些应该放在Update中。
- 调用频率差异:在低帧率情况下,FixedUpdate可能比Update调用更频繁,反之亦然,因此要根据需求选择合适的函数。
- 使用正确的时间步长:在FixedUpdate中,应使用Time.fixedDeltaTime来计算与时间相关的逻辑,确保结果与固定步长一致。
- 输入处理:虽然可以在FixedUpdate中直接处理输入,但更推荐的做法是在Update中捕获输入并保存,然后在FixedUpdate中使用,以避免输入延迟或遗漏。
六、FixedUpdate调试工具与技巧
1. 可视化FixedUpdate调用
public class FixedUpdateVisualizer : MonoBehaviour
{
public Text fixedUpdateCounterText;
private int fixedUpdateCount = 0;
private float resetTimer = 0;
void FixedUpdate()
{
fixedUpdateCount++;
}
void Update()
{
resetTimer += Time.deltaTime;
if (resetTimer >= 1.0f)
{
fixedUpdateCounterText.text = $"FixedUpdate调用: {fixedUpdateCount}/秒";
fixedUpdateCount = 0;
resetTimer = 0;
}
}
}
2. 物理设置的统一管理
// 统一的物理设置管理
public class PhysicsManager : MonoBehaviour
{
[Range(0.005f, 0.05f)]
public float fixedTimestep = 0.02f;
[Range(1, 10)]
public int solverIterations = 6;
void Start()
{
Time.fixedDeltaTime = fixedTimestep;
Physics.defaultSolverIterations = solverIterations;
}
// 允许在运行时调整
void OnValidate()
{
if (Application.isPlaying)
{
Time.fixedDeltaTime = fixedTimestep;
Physics.defaultSolverIterations = solverIterations;
}
}
}
七、常见问题与解决方案
1. 物理不稳定性
// 问题示例:在Update中直接修改刚体变换
void Update()
{
// 问题代码:导致物理不稳定
transform.position += new Vector3(0, 1, 0) * Time.deltaTime;
}
// 解决方案:使用FixedUpdate和物理API
void FixedUpdate()
{
// 正确方法
rb.MovePosition(rb.position + new Vector3(0, 1, 0) * Time.fixedDeltaTime);
}
2. 物理与动画的协调
// 物理与动画混合的正确方式
void FixedUpdate()
{
// 先更新物理
UpdatePhysics();
// 然后在物理更新后应用动画根运动
if (animator.applyRootMotion)
{
Vector3 rootMotionDelta = animator.deltaPosition;
rb.MovePosition(rb.position + rootMotionDelta);
}
}
3. 适应不同设备的物理步长
void Start()
{
// 基于设备性能调整物理步长
if (SystemInfo.processorFrequency < 2000) // 低于2GHz
{
// 低性能设备使用较大步长
Time.fixedDeltaTime = 0.03f;
}
else
{
// 高性能设备使用较小步长
Time.fixedDeltaTime = 0.01667f; // 约60Hz
}
}
八、总结与最佳实践
- 明确分工:
- FixedUpdate:所有物理计算、力的应用、刚体操作
- Update:输入检测、游戏逻辑、非物理动画
- 保持一致的时间度量:
- 在FixedUpdate中使用Time.fixedDeltaTime
- 避免在FixedUpdate中使用Time.deltaTime
- 适当设置:
- 根据游戏物理需求调整Fixed Timestep
- 考虑目标平台性能与物理精度权衡
- 输入与物理解耦:
- 在Update中检测输入
- 通过状态变量将输入传递给FixedUpdate
- 避免误用:
- 不要在FixedUpdate中放置大量非物理计算
- 避免在FixedUpdate中进行复杂的AI决策逻辑
通过正确理解和应用FixedUpdate,可以构建物理行为一致、性能高效的游戏体验,尤其是在需要精确物理模拟的项目中。