【Unity】Rigidbody.velocity 的陷阱

说在前面

本人仍是Unity初学者,文章中如有错误恳请你们可以指正。若是有其余的想法也欢迎来交流,你们共同窗习,共同进步。web

正文

咱们常在Unity开发中直接使用Rigidbody.velocity属性来获取刚体的当前速度,这在大多数状况下是没有问题的。但在某些状况下这么作就可能得不到咱们想要的结果。好比经过transform.Translate(), transform.RotateAround(), rigidbody.MovePosition(), Vector3.MoveTowards() 等方法 “强制” 改变刚体的运动状态时,此时物体速度的改变并不会引发Rigidbody.velocity的改变。svg


不信?那咱们先在Unity中作个实验,看看是否是这样。函数

为了探讨Rigidbody.velocity到底准不许确,咱们必须找到一种可以相对准确地计算速度的方式来做为对照。我这里采起的方式是定义一个Vector3变量记录刚体在上一帧的position,计算速度时在Update()中将当前帧物体的位置减去上一帧存储的物体位置,再除以帧的刷新间隔deltaTime,获得的就是物体的瞬时速度,最后在Update()结尾更新Vector3变量的值供下一帧进行计算。咱们姑且将这种方法获得的值做为游戏场景中的 “ 真实速度 ”学习


先构建一个这样的场景,如图所示。场景中有水平的地面,以及并排放置在地面上的红黄蓝三个小球,三个小球都被赋予了刚体属性。

我给这三个小球分别建立并绑定了red.cs,yellow.cs,blue.cs 这三个脚本,下面放代码:spa

// red.cs
using UnityEngine;
public class red : MonoBehaviour
{
    private Transform m_transform;
    private Rigidbody body;
    private Vector3 lastPos;
    private int frameNum = 0; //记录当前是第几帧

    void Awake()
    {
        m_transform = transform;
        body = GetComponent<Rigidbody>();
        lastPos = m_transform.position;//经过求相邻两帧物体的位移长度和时间间隔
                                       //deltaTime的比值计算物体瞬时速率
    }

    void FixedUpdate()
    {
        //使用rigidbody.MovePosition()移动物体
        body.MovePosition(transform.position + Vector3.forward * Time.deltaTime);

        float realspeed = (m_transform.position - lastPos).magnitude / Time.deltaTime;
        lastPos = m_transform.position;
        frameNum++;

        Debug.Log("第" + frameNum + "帧 " + "【仅移动】" + "真实速率 :" + realspeed +"Rigidbody.velocity:" + body.velocity.magnitude);
    }
}

因为另外两个脚本只有FixedUpdate()与上面不一样,下面只贴出这一部分:code

//blue.cs   
void FixedUpdate()
{
    //使用rigidbody.AddForce()添加力的做用
    body.AddForce(Vector3.forward * 5);

    float realspeed = (m_transform.position - lastPos).magnitude / Time.deltaTime;
    lastPos = m_transform.position;
    frameNum++;

    Debug.Log("第" + frameNum + "帧 " + "【仅受力】" + "真实速率 :" + realspeed +"Rigidbody.velocity:" + body.velocity.magnitude);
}
//yellow.cs 
void FixedUpdate()
{
    //使用rigidbody.MovePosition()移动物体
    //同时使用rigidbody.AddForce()添加力的做用
    body.MovePosition(transform.position + Vector3.forward * Time.deltaTime);
    body.AddForce(Vector3.forward * 5);

    float realspeed = (m_transform.position - lastPos).magnitude / Time.deltaTime;
    lastPos = m_transform.position;
    frameNum++;

    Debug.Log("第" + frameNum + "帧 " + "【混合变换】" + "真实速率 :" + realspeed +"Rigidbody.velocity:" + body.velocity.magnitude);
}

因而可知,三个代码的不一样处仅仅在于,red.cs 使用 MovePosition() 方法使红球运动,blue.cs 使用 AddForce() 方法使蓝球运动,yellow.cs 将这两种方法共同做用于黄球。除此以外,它们都用相同的方法计算瞬时速度。将类似的功能分红三个脚原本写是为了能自定义脚本执行的顺序,从而方便在Console面板上提取信息。在脚本中,咱们将各自通过计算获得的瞬时速度大小和Rigidbody.velocity的大小在Console面板上输出进行比较。运行后结果以下:orm

这里写图片描述
这里写图片描述

在【仅移动】的脚本中,MovePosition(transform.position + Vector3.forward * Time.deltaTime)这一语句使红色球在deltaTime的时间内移动 Vector3.forward * Time.deltaTime的距离,即真实速率为1,这与该脚本在Console中的输出一致。但与此同时,它的Rigidbody.velocity却一直显示为0,这验证了我以前说的结论。

除此以外,还能发现一些有趣的现象:xml

  • 在小球【仅受力】的做用时,Rigidbody.velocity能和真实速率保持一致
  • 当小球既受力又作MovePosition变换时, 真实速率是同一帧【仅移动】和【仅受力】中真实速率的和,Rigidbody.velocity和【仅受力】一致。

而当咱们把全部球的刚体组件中的Is Kinematic属性勾选上后,咱们再来看看运行结果:
这里写图片描述
OMG!【仅移动】小球的Rigidbody.velocity竟然又和真实速率一致了!blog

本场景中使用MovePosition()做为【仅移动】变换的函数,事实上transform.Translate(), transform.RotateAround(), rigidbody.MovePosition(), Vector3.MoveTowards() 等等亦能产生相同的结果,这里再也不重复实验。游戏

分析

我并不了解Rigidbody.velocity这个属性在内部是如何被定义的,官方文档没有相关的说明,网上也没有找到相关的资料,我我的只能根据这些现象作以下的一些推测。

当刚体的Is Kinematic没有被勾选时,刚体的运动就被Unity的物理引擎所掌控,物体的运动和状态都会遵循真实世界的物理定律。咱们知道,在牛顿力学中,要改变一个物体的运动状态必需要对其施加力,Unity也为咱们提供了AddForce()方法。然而像MovePosition()这样的方法彷佛可让物体的运动为所欲为,可以以任意速度到达任意位置,可让物体瞬间加到一个很是大的速度。显而易见,这种对运动状态的 “ 强制 ” 改变一定不能经过加力的方式实现,这就已经脱离了真实世界的物理定律了。被物理引擎控制的物体擅自进行了不按套路的操做,Rigidbody.velocity就不会记录这种 “非法” 操做带来的速度改变,或者将这种非法操做对velocity的改变视为0。

反之,当刚体的Is Kinematic被勾选时,刚体的运动就脱离了Unity的物理引擎控制。风水轮流转,天道好轮回,这种状况下MovePosition()成了合法操做,AddForce()成了非法操做了。想要报仇雪恨的MovePosition()积攒了多年的怨气,对非法操做的限制变得更为严格,以前的状况还容许非法操做对物体运动状态的改变,此次已经彻底屏蔽了AddForce()的做用。从上一张截图就可看出,此次即使加力物体也始终保持静止。此时此刻MovePosition()终于做为合法操做被Rigidbody.velocity承认,使其可以反映物体真实速率。

总结

经过以上案例,个人想法就是最好不要对未勾选Is Kinematic的刚体使用transform.Translate(), transform.RotateAround(),rigidbody.MovePosition(),Vector3.MoveTowards()等等这些方法,毕竟这些很是规操做一定会对物理模拟的真实性产生影响。若是你不得不使用时,也请注意Rigidbody.velocity并非物体在场景和游戏视图中的真实速度,不要滥用这些方法和这个属性而不当心掉入它的 “ 陷阱 ”。

因为不清楚实现机制因此没法总结更多,欢迎你们踊跃发言,建言献策。