Unity中刚体撞墙抖动的原因和本质
当我们制作角色移动的时候我们都知道使用设置位置的方法来移动一个带有刚体和碰撞体的物体,遇到碰撞体的时候就会抖动。
上网查找原因,都说是和物理系统冲突导致的,然后再也找不到其他线索。
这个说法,对,但它并不是最本质的原因。
我们都知道,fixedUpdate是固定间隔更新物理,在一帧中可能没有执行fxiedUpdate,也就是说没有更新物理。但是如果在一帧中执行了fixedUpdate,那么它一定是在Update之前更新的。
让我们考虑常见的移动代码:
public float speed=3;
private Vector3 moveVec;
private void Update()
{
var horizontal = Input.GetAxis("Horizontal");
var vertical = Input.GetAxis("Vertical");
moveVec = new Vector3(horizontal,0,vertical);
moveVec = Vector3.ClampMagnitude(moveVec, 1);
Move(moveVec*speed,Time.deltaTime);
}
void Move(Vector3 vec, float deltaTime)
{
transform.position += vec * deltaTime;
}
我们在移动的时候撞到墙时一定会抖动的。
让我们分析一下:
第1帧:
fixedUpdate,啥也不干
update,把物体移动到墙内。
第2帧:
fxiedUpdate,物理系统把物体移出到墙外
update,把物体再次移动到墙内。
第3帧:
fxiedUpdate,物理系统把物体移出到墙外
update,把物体再次移动到墙内
这样来看,相当于update覆盖了fixedUPdate的操作,不应该抖动啊?
我想了一个小时,chatgpt,deepseek各种都问过,都说我分析的没问题,但就是会抖动。
后来我终于想通了。
最本质的原因其实就是移动代码:transform.position += vec * deltaTime;
此代码等于transform.position = transform.position+vec * deltaTime;
也就是说他用的是当前位置来计算新的位置。
行为就变成了如下:
第1帧:
fixedUpdate,啥也不干
update,把物体移动到墙内。
第2帧:
fxiedUpdate,物理系统把物体移出到墙外位置A。
update,transform.position=位置A+vec * deltaTime;此时位置为B
第3帧:
fxiedUpdate,物理系统把物体移出到墙外位置C。
update,transform.position=位置C+vec * deltaTime;,此时位置为D
也就是说撞墙时每次移动是在物理系统处理过的位置上再进行移动,而每次物理系统又因为间隔,和上一次的位置导致挤出的位置不同所以导致抖动。
上面说到,和物理系统的冲突不是最本质的原因,为什么?
因为这里导致抖动的根源还是因为使用了当前位置来进行计算。
假设我们把代码改成如下:
private Vector3 startPos;
private float elapsedTime;
// Start is called before the first frame update
void Start()
{
startPos = transform.position;
}
// Update is called once per frame
void Update()
{
elapsedTime += Time.deltaTime;
var targetPos = startPos + Vector3.right * 10;
transform.position = Vector3.Lerp(startPos, targetPos, elapsedTime/10);
}
这个代码是10秒内从起始位置移动到右边10米处,他没有使用当前位置,而是每帧设置新的位置。
可以看到畅通无阻的进入了方块,我没有改刚体的任何设置。这段移动中也有物理系统参与反应,并且在fixedUpdate中把它挤了出去,但是小球在update中设置新位置,所以覆盖了物理系统的挤出结果,看起来就像无视了物理系统一样。