3.2 物理引擎与 FixedUpdate:早班物理岗


文档摘要

3.2 物理引擎与 FixedUpdate:早班物理岗 本节摘要:物理引擎按固定时间步模拟世界,FixedUpdate 是唯一被授权驱动刚体的班次。本节讲清刚体、重力、时间步三者的关系,示范"不抖、不穿、不飘"的角色移动写法,并解释高速物体穿墙这类经典现象的成因与对策。 刚体上岗:把物体交给物理引擎 第 1 章说过 GameObject 本身是空工位。给工位挂上 Rigidbody(刚体)组件,等于宣布"这个物体从此交给物理引擎托管":它会受重力下坠、会与其他碰撞体互相推挤、会保留惯性。刚体身上几个属性决定了物理行为的基本盘: Mass 质量:影响碰撞时的动量交换,别把万物都设成 1,卡车的质量就该比纸箱大两个数量级;

3.2 物理引擎与 FixedUpdate:早班物理岗

本节摘要:物理引擎按固定时间步模拟世界,FixedUpdate 是唯一被授权驱动刚体的班次。本节讲清刚体、重力、时间步三者的关系,示范"不抖、不穿、不飘"的角色移动写法,并解释高速物体穿墙这类经典现象的成因与对策。

刚体上岗:把物体交给物理引擎

第 1 章说过 GameObject 本身是空工位。给工位挂上 Rigidbody(刚体)组件,等于宣布"这个物体从此交给物理引擎托管":它会受重力下坠、会与其他碰撞体互相推挤、会保留惯性。刚体身上几个属性决定了物理行为的基本盘:

  • Mass 质量:影响碰撞时的动量交换,别把万物都设成 1,卡车的质量就该比纸箱大两个数量级;
  • Drag 与 Angular Drag 阻尼:空气阻力,跳跃下落想更快就调大下落方向的阻力或手动施加额外重力;
  • Use Gravity:受不受重力,顶视角游戏通常关掉;
  • Constraints 约束:锁定某些轴的位置或旋转——2D 感觉的 3D 角色,锁掉 Z 轴移动与 X、Y 轴旋转是常规操作;
  • Collision Detection 碰撞检测模式:Continuous(连续)与 Discrete(离散),穿墙问题的开关,后面细说。

与之搭档的是 Collider(碰撞器),它定义物体的物理外形——刚体是"让物理引擎管它",碰撞器是"它的身体长什么样"。两者关系在第 3.3 节展开,本节先把驱动端的纪律立起来。

为什么物理逻辑必须住进 FixedUpdate

渲染帧率是浮动的:60 帧的机器一帧 16 毫秒,掉到 30 帧就变成 33 毫秒。物理求解器要求数值稳定,不能跟着帧率波动,所以引擎用固定时间步(默认 0.02 秒,即每秒 50 次)驱动物理:每积累够 0.02 秒就跑一次物理步。FixedUpdate 就挂在这个节拍上——它不是"每帧一次",而是"每个物理步一次"。

由此推出物理岗三条纪律。第一条,驱动刚体只用 FixedUpdate:在 Update 里每帧给刚体加一次力,帧率越高力加得越频繁,60 帧与 144 帧的机器上角色速度完全不同。第二条,别直接改刚体物体的 transform.position:那相当于趁物理引擎不注意把物体瞬移过去,速度信息全部丢失,碰撞求解会乱,正确入口是 velocity 赋值或 MovePosition。第三条,物理读写只在 FixedUpdate 与碰撞回调里做,Update 里读到的刚体数据可能是两次物理步之间的中间态。

一帧里的物理节拍

一帧里的物理节拍

动手:把角色真的驱动起来

背景:平台跳跃主角需要左右移动与跳跃,且手感稳定不随帧率漂移。

操作:给角色挂 Rigidbody 与 Capsule Collider(或 2D 项目的对应物),脚本如下:

using UnityEngine; [RequireComponent(typeof(Rigidbody))] public class PhysicController : MonoBehaviour { public float moveSpeed = 5f; // 水平移动速度 public float jumpForce = 7f; // 跳跃冲量 public Transform groundCheck; // 脚底检测点(空物体) public float groundRadius = 0.15f; public LayerMask groundMask; // 哪些层算地面 Rigidbody rb; bool jumpQueued; // Update 记录的跳跃意图 void Awake() { rb = GetComponent<Rigidbody>(); // 锁定旋转,角色不能被撞翻 rb.constraints = RigidbodyConstraints.FreezeRotation; } void Update() { // 输入采集只在 Update:跳跃是边沿事件,先排队 if (Input.GetKeyDown(KeyCode.Space)) { jumpQueued = true; } } void FixedUpdate() { // 水平移动:直接改 velocity 的水平分量,保留垂直分量 float h = Input.GetAxisRaw("Horizontal"); Vector3 v = rb.velocity; v.x = h * moveSpeed; rb.velocity = v; // 落地检测:在脚底画一个小球,看有没有碰到地面层 bool onGround = Physics.CheckSphere( groundCheck.position, groundRadius, groundMask); // 消费跳跃意图:落地且排队过才执行 if (jumpQueued && onGround) { rb.AddForce(Vector3.up * jumpForce, ForceMode.VelocityChange); } jumpQueued = false; // 无论是否执行都清空,防止落地瞬间意外起跳 } }

结果:角色左右移动干脆,按空格起跳一次,落地前再按不生效;把帧率限制从 60 改到 30(质量设置里改 VSync),移动手感不变。

解读:四个细节撑起手感。其一,[RequireComponent] 声明脚本依赖刚体,挂载时自动补齐组件——防呆从声明开始。其二,跳跃意图用"排队加消费"模式跨过 Update 与 FixedUpdate 的节拍差:只靠 FixedUpdate 里读 GetKeyDown 会漏键(帧内可能跑零次物理步)。其三,velocity 赋值只改水平分量、保留垂直分量,否则跳跃上升途中左右移动会把下落清零,角色变直升机。其四,ForceMode.VelocityChange 忽略质量直接改速度,跳跃高度不受质量调参影响,调手感更直观。

变式:把 moveSpeed 的速度控制换成加速度模型——用 rb.AddForce 输入方向乘加速度,再靠 Drag 收敛速度上限。手感从"街机式即停即走"变成"推箱子式的惯性加速",赛车与冰面关卡都靠这一变式。

高速穿墙与检测模式

子弹打穿薄墙是物理模拟的固有问题:物理步之间物体是"瞬移"的,一步之内飞过了墙的厚度,碰撞就没发生。对策是碰撞检测模式:Discrete(默认)每步只查一次位置;Continuous 会做连续检测,代价是计算量上升。实操原则:主角与高速投射物开 Continuous,静止墙体保持默认;再配合"墙体别做得太薄"的建模纪律,穿墙基本绝迹。若还有穿模,先怀疑移动代码是不是绕过了物理(直接改 position),而不是怪引擎。

本节要点回顾

  • 刚体托管物理行为:质量、阻尼、约束、检测模式四个属性定基本盘;
  • FixedUpdate 按固定步长跑:一帧零到多次,帧率无关的手感全靠它;
  • 驱动刚体的正门:velocity 与 MovePosition,直接改 transform.position 是事故源;
  • 意图排队模式:Update 采集、FixedUpdate 消费,跨节拍不丢键;
  • 穿墙三件套:高速物体开 Continuous、墙别太薄、别绕过物理移动。

物体被驱动后就要见面了。下一节讲碰撞事件的分发规则:实心的墙与虚拟的感应区,走的是两套不同的通知名单。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U