Unity 物理系列二 AddForce velocity

一、AddForce ForceMode

https://docs.unity3d.com/cn/2019.4/ScriptReference/Rigidbody.AddForce.html
https://docs.unity3d.com/cn/2019.4/ScriptReference/ForceMode.html
Unity中关于作用力方式ForceMode的功能注解

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ForceMode_ts : MonoBehaviour
{
    public Rigidbody A, B, C, D;
    //作用力向量
    Vector3 forces = new Vector3(10.0f, 0.0f, 0.0f);

    void Start()
    {
        //初始化4个刚体的质量,使其相同
        A.mass = 2.0f;
        B.mass = 2.0f;
        C.mass = 2.0f;
        D.mass = 2.0f;
        //对A、B、C、D采用不同的作用力方式
        //注意此处只是对物体增加了1帧的作用力
        //如果要对刚体产生持续作用力请把以下代码放在FixedUpdate()方法中
        A.AddForce(forces, ForceMode.Force);
        B.AddForce(forces, ForceMode.Acceleration);
        C.AddForce(forces, ForceMode.Impulse);
        D.AddForce(forces, ForceMode.VelocityChange);
    }

    void FixedUpdate()
    {
        Debug.Log("ForceMode.Force作用方式下A每帧增加的速度:" + A.velocity);
        Debug.Log("ForceMode.Acceleration作用方式下B每帧增加的速度:" + B.velocity);
        Debug.Log("ForceMode.Impulse作用方式下C每帧增加的速度:" + C.velocity);
        Debug.Log("ForceMode.VelocityChange作用方式下D每帧增加的速度:" + D.velocity);
    }
}

功能注解:ForceMode为枚举类型,用来控制力的作用方式,有4个枚举成员,在以下举例中均设刚体质量为m=2.0f,力向量为f=(10.0f,0.0f,0.0f)。

1.ForceMode.Force

默认方式,使用刚体的质量计算,以每帧间隔时间为单位计算动量。设FixedUpdate()的执行频率采用系统默认值(即0.02s),,则由动量定理f•t=m•v可得:10*0.02=2*v1,从而可得v1=0.1,即每帧刚体在X轴上值增加0.1米,从而可计算得刚体的每秒移动速度为v2=(1/0.02)*v1=5m/s。

2.ForceMode.Acceleration

在此种作用方式下会忽略刚体的实际质量而采用默认值1.0f,时间间隔以系统帧频间隔计算(默认值为0.02s),即f•t=1.0•v即可得v1= f•t=10*0.02=0.2,即刚体每帧增加0.2米,从而可得刚体的每秒移动速度为v2=(1/0.02)*v1=10m/s。

3.ForceMode.Impulse

此种方式采用瞬间力作用方式,即把t的值默认为1,不再采用系统的帧频间隔,即f•1.0=m•v即可得v1=f/m=10.0/2.0=5.0,即刚体每帧增加5.0米,从而可得刚体每秒的速度为v2=(1/0.02)*5.0=250m/s。

4.ForceMode.VelocityChange

此种作用方式下将忽略刚体的实际质量,采用默认质量1.0,同时也忽略系统的实际帧频间隔,采用默认间隔1.0,即f•1.0=1.0•v即可得v1=f=10.0,即刚体每帧沿X轴移动距离为10米,从而可得刚体每秒的速度为v2=(1/0.02)*v1=500m/s。

二、velocity

https://docs.unity.cn/cn/2019.4/ScriptReference/Rigidbody-velocity.html
刚体的速度矢量。它表示刚体位置的变化率。

在大多数情况下,不应该直接修改速度,因为这可能导致行为失真 - 改用 AddForce

请勿在每个物理步骤中设置对象的速度,这将导致不真实的物理模拟。 需要更改速度的一个典型用法是第一人称射击游戏中的跳跃动作设计,因为此时需要立即更改速度。

1.点击空格键,角色会跳跃。而有时会跳的比之前高很多(使用Rigidbody.AddForce)

酷游戏的一些bug总结(滥用FixedUpdate的坑)
Unity3D中Rigidbody.velocity和Addforce的区别
假设我们想要做一个2D的跳跃游戏,在这个游戏里我希望我按下跳跃键的时候,游戏物体的跳跃高度是恒定的。

那么,如果此时我使用的是addForce显然不会满足我的效果,因为经过测试,每按一下跳跃键,它会被施加一个恒定的力,它跳跃的初始速度会越变越大,每次跳跃的高度和前一次相较变得越来越大(在连续跳跃的情况下),也就是可以理解成,我现在被施加了一个大小为50的力,在重力作用下我的力的效果在减小,在正向速度还没有完全消失的情况下,此时我又按了一下跳跃键,那么就会在这个力的基础上再施加一次大小为50的力,物体会越跳越快,越跳越高。

而此时如果我们使用的是velocity方法改变它的初始速度到我们希望的值和方向,那么当我们按下跳跃键的时候,无论此时物体减速到何种情况,也最多只会提升到我们规定的速度以及朝向我们规定的方向。初始速度不变的情况下,跳跃高度也是恒定的。

解决:
AddForce之前应该清一下速率,因为是射线检测是否碰到地面,而跳跃后不会立刻离地。如果不清,后面几帧会累加AddForce。清除速率保证离地的一瞬间肯定是正常Force,而非累加Force。

2.velocity

参考【Unity】Rigidbody.velocity 的陷阱,这里原文比较长,做了详细的测试,本文转载结论:

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

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

  • 在【仅移动】的脚本中,MovePosition(transform.position + Vector3.forward * Time.deltaTime)这一语句使红色球在deltaTime的时间内移动 Vector3.forward * Time.deltaTime的距离,即真实速率为1,这与该脚本在Console中的输出一致。但与此同时,它的Rigidbody.velocity却一直显示为0
  • 在小球【仅受力】的作用时,Rigidbody.velocity能和真实速率保持一致
  • 当小球既受力又做MovePosition变换时, 真实速率是同一帧【仅移动】和【仅受力】中真实速率的和,Rigidbody.velocity和【仅受力】一致。

而当我们把所有球的刚体组件中的Is Kinematic属性勾选上后,我们再来看看运行结果:


image.png

OMG!【仅移动】小球的Rigidbody.velocity居然又和真实速率一致了!

我并不了解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并不是物体在场景和游戏视图中的真实速度,不要滥用这些方法和这个属性而不小心掉入它的 “ 陷阱 ”。

煮粥侠_99:虽然文章很久远了,但是想说明一点:原理其实unity的手册有解释,看这里 https://docs.unity3d.com/2019.3/Documentation/Manual/class-Rigidbody2D.html。博主的分析结果是对的,其原理,根据手册总计起来主要就是 1.用刚体的对象不要使用transform改变位置,因为这会覆盖刚体进行的位移。2. Kinematic类型的刚体不接受力学。3.Dynamic类型的刚体不建议使用rigidbody.MovePosition()方法,这个方法就是为Kinematic类型刚体准备的。所以就是要根据使用的刚体类型,选择合适的位移方法。说是Velocity的陷阱有点吓人了,其实velocity是无辜的

三、恒定力

https://docs.unity.cn/cn/2019.4/Manual/class-ConstantForce.html
恒定力 (Constant Force) 可用于快速向刚体添加恒定力。如果不希望某些一次性对象以较大的速度开始而是逐渐加速(比如火箭),则很适合使用恒定力。

要制作一个向前加速的火箭,请将 Relative Force 设定为沿正 z 轴。然后,使用刚体的 Drag 属性使其不超过某个最大速度(阻力越高,最大速度越低)。在刚体中,还要确保关闭重力,以便火箭始终保持在其路径上。

你可能感兴趣的:(Unity 物理系列二 AddForce velocity)