問題背景
當一個物體在兩個帶碰撞體的方塊組成的牆角時,只要“擠一擠”就可以從牆角穿牆而過:


原因分析
根據【Unity】Rigidbody.velocity 的陷阱一文,有以下結論:
使用transform.Translate(), transform.RotateAround(), rigidbody.MovePosition(), Vector3.MoveTowards() 等方法 “強制” 改變剛體的運動狀態時,此時物體速度的改變並不會引發Rigidbody.velocity的改變,
- 如果僅使用
MovePosition()控制物體移動(稱之為“僅移動”),剛體組件的velocity會一直為0 - 如果僅使用
AddForce()控制物體移動(稱之為“僅受力”),它的velocity與真實速率一致 - 如果同時使用
MovePosition()和AddForce()控制物體移動,它的真實速率是“僅移動”和“僅受力”的效果之和,但velocity的數值和“僅受力”時一致
也就是說,物理系統只能理解由velocity的變化而產生的運動(施加力->產生加速度->產生速度/直接修改velocity),當對一個由物理系統控制的剛體使用物理系統以外的方法施加運動時,物理系統就無法正確處理該物體的物理行為了,而上述的穿牆bug也正是因為角色本身的移動是由MovePosition()控制的,導致物理系統無法正確處理在牆角處的碰撞判定
解決方案
通過修改velocity來實現角色移動:
private void Move() {
var speed = _properties.speed; //在編輯器中設置的角色的速度
var verAxis = Input.GetAxisRaw("Vertical");
var horAxis = Input.GetAxisRaw("Horizontal");
Vector3 input = new Vector3(horAxis, 0, verAxis);
_rigidbody.velocity = input * speed;
}
注意事項
transform.Translate()可能比較好理解,畢竟是通過直接修改transform組件來完成的運動,物理系統無法理解很正常,但是為什么rigidbody.MovePosition()也不行呢?翻一翻官方文檔可以發現,Unity官方對這個方法的描述是:

“移動kinematic剛體到指定位置”,即該方法僅作用於運動學剛體,即不受物理系統控制,需要由各種變換方法直接控制物體移動的剛體,不適用於普通的遵循力學規律的剛體。
