本人仍是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
而当咱们把全部球的刚体组件中的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并非物体在场景和游戏视图中的真实速度,不要滥用这些方法和这个属性而不当心掉入它的 “ 陷阱 ”。
因为不清楚实现机制因此没法总结更多,欢迎你们踊跃发言,建言献策。