理解Unity3d的ForceMode | Understanding ForceMode in Unity3D
2015-10-18 22:52
826 查看
关于ForceMode,Unity3d官方文档写的太粗略,看完这篇你就明白了。
本文大部分都是摘自社区里一个叫Bunny83的哥们自己写代码试验出来的总结以及对其他人的回复,非常感谢这样的娃.为这个问题我google了好多天,下面的内容估计是你能搜到的最全最靠谱的解释了.
While fiddling with the player handing for Chalo Chalo, it became important for me to get a better grasp on the differences between the four force modes in Unity3D.
If you have objects that use Unity's physics system, via a rigidbody component, you can add forces to them to get them to move. Forces can be added using one of four different 'modes'. The names of these modes aren't very enlightening and I don't think the Unity3D documentation is very clear about their differences. For my own reference, and in case it helps others, this is what I've figured out.
ForceMode.Force. If the AddForce call occurs in a FixedUpdate loop, the full force supplied to the AddForce call will only have been exerted on the rigidbody after one second. Think of it as 'Force exerted per second'
ForceMode.Acceleration. Like ForceMode.Force, except the object's mass is ignored. The resulting movement will be as though the object has a mass of 1. The following lines will give the same result
ForceMode.Impulse. The entirety of the force vector supplied to the AddForce call will be applied all at once, immediately.
ForceMode.VelocityChange. Like ForceMode.Impulse except the object's mass is ignored. So the following lines give the same result:
I made a little test to verify this. The script demonstrates how the ForceModes work by canceling out their differences through modifying the values passed to the AddForce call.
Here's the C# script that was attached to each of the cubes. In the inspector, the forceMode property of each was set to use one of the four modes.
ForceMode.VelocityChange does exactly what the name suggests. So those two lines do exactly the same:
ForceMode.Acceleration is ment to be applied every FixedUpdate. So the result you get after 1 second is the same as VelocityChange. The next two lines are exactly the same:
ForceMode.Force and ForceMode.Impulse just do the same but also divide by the mass of the object:
ForceMode.Impulse:
edit
Two quick crosslinks to how drag is applied:
http://answers.unity3d.com/questions/819273/force-to-velocity-scaling.html#answer-819474
http://forum.unity3d.com/threads/drag-factor-what-is-it.85504/#post-1827892
You might want to read my answer over here
edit
In addition to my answer about how AddForce actually works, here's how the drag is applied (at least in version 4.5.4f1):
You might want to read my post on the forum where i explain it a bit more in detail.
second edit
If you want to use Unity's drag system, here are 6 helper methods to calculate:
the final velocity you might reach for a given acceleration / velocityChange and drag value.
the drag value required for a given acceleration / velocityChange to reach the desired velocity.
the acceleration / velocityChange required to reach the desired velocity with a given drag value
You could extend each pair of methods to work with an actual force / impulse value. Keep in mind those methods only work for an acceperation / velocity change applied each FixedUpdate. One-time changes are pointless since you will have the final velocity at the moment you apply the force / acceleration / change. The drag will simply pull it back to 0.
Final note: In case a constant force is applied with a drag value > 0, the velocity will slowly get closer to the final velocity but won't actually reach it. However due to floating point precision the remaining difference will usually be "rounded away" within seconds.
Also keep in mind that in case your drag value is greater than the fixed framerate, functions like GetRequiredAcceleraton will return infinity as there is no acceleration to reach the given speed.
Just made a few tests and it looks like the formula is this:
Code (csharp):
CallFixedUpdate();
velocity = ApplyForces(velocity);
velocity *= Mathf.Clamp01(1-drag * dt);
velocity = ApplyCollisionForces(velocity);
position += velocity * dt;
Some facts about this:
"Manual" forces are applied before the drag
Drag reduces the velocity by a "fix" percentage based on the current fixedDeltaTime and drag value
If the drag value equals or is greater than the fixed frame rate (1/fixedDeltaTime) the rigidbody can't move on it's own.
Since collisions are calculated / applied after the drag, they can cause an impuls spike in velocity and movement, but the next fixed frame the velocity will be 0 again unless there are consequential collisions which again apply an impule force. Note: as long as a collision is "active" (as long as collision stay is called) there might be forces applied from the colliding rigidbody. However all momentum that got tranferred will completely vanish the next frame.
Example: If you have set your fixedDeltaTime to 0.01 you will get 100 FixedUpdates per second. If you set the drag value to 50 and initial velocity of 100 will be cut in half each fixed frame because the "percentage multiplier" is (1 - (0.01 * 50)) == 0.5
If the drag is 100 or greater the multiplier will be 0 ( == 1 - (0.01*100)). Keep in mind that the default setting for the fixedDeltaTime is 0.02 which gives you a max drag of 50
I haven't tested when and how Joints apply their forces. If they use the "normal" AddForce mechanics the drag could draw the Joint completely useless. If it's appllied internally after the drag it might have an affect. But keep in mind that no velocity will survive the next frame if your drag >= 1 / fixedDeltaTime
Conclusion: That's simply a strange approach. I'm not sure if this was different in the past, but anyways the "hint" on the drag field is clearly wrong since a range of (0 to infinity) makes no sense. The manual states the same, so i guess they *wanted* to do something like HarvesteR suggested as the second example:
Code (csharp):
velocity *= 1 / (1 + drag*dt);
but somehow they didn't.
Determined and tested using Unity 4.5.4f1
Derived from Bunny83's answer, the following script is a stand-alone approximate implementation of Unity's drag:
Code (CSharp):
using UnityEngine;
public class PhysicsDrag : MonoBehaviour
{
public float drag;
private Rigidbody Body;
private void Start()
{
Body = GetComponent<Rigidbody>();
Body.drag = 0f;
}
private void FixedUpdate()
{
Body.velocity *= Mathf.Clamp01(1f - drag * Time.fixedDeltaTime);
}
}
With just a bit more code, you can re-implement both Unity's gravity and drag, and achieve perfect results:
Code (CSharp):
using UnityEngine;
public class PhysicsGravityAndDrag : MonoBehaviour
{
public float drag;
private Rigidbody Body;
private void Start ()
{
Body = GetComponent<Rigidbody>();
Body.useGravity = false;
Body.drag = 0f;
}
private void FixedUpdate()
{
var velocity = Body.velocity;
velocity += Physics.gravity * Time.fixedDeltaTime;
velocity *= Mathf.Clamp01(1f - drag * Time.deltaTime);
Body.velocity = velocity;
}
}
Many thanks to Bunny83 for finally leading me to the solution to my own drag-calculation troubles.
本文大部分都是摘自社区里一个叫Bunny83的哥们自己写代码试验出来的总结以及对其他人的回复,非常感谢这样的娃.为这个问题我google了好多天,下面的内容估计是你能搜到的最全最靠谱的解释了.
While fiddling with the player handing for Chalo Chalo, it became important for me to get a better grasp on the differences between the four force modes in Unity3D.
If you have objects that use Unity's physics system, via a rigidbody component, you can add forces to them to get them to move. Forces can be added using one of four different 'modes'. The names of these modes aren't very enlightening and I don't think the Unity3D documentation is very clear about their differences. For my own reference, and in case it helps others, this is what I've figured out.
ForceMode.Force. If the AddForce call occurs in a FixedUpdate loop, the full force supplied to the AddForce call will only have been exerted on the rigidbody after one second. Think of it as 'Force exerted per second'
ForceMode.Acceleration. Like ForceMode.Force, except the object's mass is ignored. The resulting movement will be as though the object has a mass of 1. The following lines will give the same result
rigidbody.AddForce((Vector3.forward * 10),ForceMode.Force); rigidbody.AddForce((Vector3.forward * 10)/rigidbody.mass,ForceMode.Acceleration);
ForceMode.Impulse. The entirety of the force vector supplied to the AddForce call will be applied all at once, immediately.
ForceMode.VelocityChange. Like ForceMode.Impulse except the object's mass is ignored. So the following lines give the same result:
rigidbody.AddForce((Vector3.forward * 10),ForceMode.Impulse); rigidbody.AddForce((Vector3.forward * 10)/rigidbody.mass,ForceMode.VelocityChange);
I made a little test to verify this. The script demonstrates how the ForceModes work by canceling out their differences through modifying the values passed to the AddForce call.
Here's the C# script that was attached to each of the cubes. In the inspector, the forceMode property of each was set to use one of the four modes.
using UnityEngine; using System.Collections; public class force_force : MonoBehaviour{ public ForceMode forceMode; void FixedUpdate(){ AddForce(); } void AddForce(){ float massModifier=1f; float timeModifier=1f; // Modify things to make all give same result as ForceMode.Force if (forceMode==ForceMode.VelocityChange || forceMode==ForceMode.Acceleration){ massModifier=rigidbody.mass; } if (forceMode==ForceMode.Impulse || forceMode==ForceMode.VelocityChange){ timeModifier=Time.fixedDeltaTime; } rigidbody.AddForce(((Vector3.forward * 10) * timeModifier)/massModifier,forceMode); } }
ForceMode.VelocityChange does exactly what the name suggests. So those two lines do exactly the same:
// The passed "force" parameter is in m/s which is added instantly to the velocity
rigidbody.AddForce(Vector3.forward*1.0f,ForceMode.VelocityChange);
rigidbody.velocity += Vector3.forward*1.0f;
ForceMode.Acceleration is ment to be applied every FixedUpdate. So the result you get after 1 second is the same as VelocityChange. The next two lines are exactly the same:
// The passed "force" is in m/s². If applied every fixed frame the accumulated velocity will increased by "force" m/s every second
rigidbody.AddForce(Vector3.forward*1.0f,ForceMode.Acceleration);
rigidbody.velocity += Vector3.forward*1.0f * Time.fixedDeltaTime;
ForceMode.Force and ForceMode.Impulse just do the same but also divide by the mass of the object:
// "force" is in newton (kg*m/s²)
rigidbody.AddForce(Vector3.forward*1.0f,ForceMode.Force);
rigidbody.velocity += Vector3.forward*1.0f * Time.fixedDeltaTime / (rigidbody.mass);
ForceMode.Impulse:
// "force" is a impulse / momentum which is in kg*m/s
rigidbody.AddForce(Vector3.forward*1.0f,ForceMode.Impulse);
rigidbody.velocity += Vector3.forward*1.0f / rigidbody.mass;
edit
Two quick crosslinks to how drag is applied:
http://answers.unity3d.com/questions/819273/force-to-velocity-scaling.html#answer-819474
http://forum.unity3d.com/threads/drag-factor-what-is-it.85504/#post-1827892
You might want to read my answer over here
edit
In addition to my answer about how AddForce actually works, here's how the drag is applied (at least in version 4.5.4f1):
velocity *= Mathf.Clamp01(1f - drag * Time.fixedDeltaTime);
You might want to read my post on the forum where i explain it a bit more in detail.
second edit
If you want to use Unity's drag system, here are 6 helper methods to calculate:
the final velocity you might reach for a given acceleration / velocityChange and drag value.
the drag value required for a given acceleration / velocityChange to reach the desired velocity.
the acceleration / velocityChange required to reach the desired velocity with a given drag value
You could extend each pair of methods to work with an actual force / impulse value. Keep in mind those methods only work for an acceperation / velocity change applied each FixedUpdate. One-time changes are pointless since you will have the final velocity at the moment you apply the force / acceleration / change. The drag will simply pull it back to 0.
//C#
float GetFinalVelocity(float aVelocityChange, float aDrag)
{
return aVelocityChange * (1 / Mathf.Clamp01(aDrag * Time.fixedDeltaTime) - 1);
}
float GetFinalVelocityFromAcceleration(float aAcceleration, float aDrag)
{
return GetFinalVelocity(aAcceleration * Time.fixedDeltaTime, aDrag);
}
float GetDrag(float aVelocityChange, float aFinalVelocity)
{
return aVelocityChange / ((aFinalVelocity + aVelocityChange) * Time.fixedDeltaTime);
}
float GetDragFromAcceleration(float aAcceleration, float aFinalVelocity)
{
return GetDrag(aAcceleration * Time.fixedDeltaTime, aFinalVelocity);
}
float GetRequiredVelocityChange(float aFinalSpeed, float aDrag)
{
float m = Mathf.Clamp01(aDrag * Time.fixedDeltaTime);
return aFinalSpeed * m / (1 - m);
}
float GetRequiredAcceleraton(float aFinalSpeed, float aDrag)
{
return GetRequiredVelocityChange(aFinalSpeed, aDrag) / Time.fixedDeltaTime;
}
Final note: In case a constant force is applied with a drag value > 0, the velocity will slowly get closer to the final velocity but won't actually reach it. However due to floating point precision the remaining difference will usually be "rounded away" within seconds.
Also keep in mind that in case your drag value is greater than the fixed framerate, functions like GetRequiredAcceleraton will return infinity as there is no acceleration to reach the given speed.
Just made a few tests and it looks like the formula is this:
Code (csharp):
CallFixedUpdate();
velocity = ApplyForces(velocity);
velocity *= Mathf.Clamp01(1-drag * dt);
velocity = ApplyCollisionForces(velocity);
position += velocity * dt;
Some facts about this:
"Manual" forces are applied before the drag
Drag reduces the velocity by a "fix" percentage based on the current fixedDeltaTime and drag value
If the drag value equals or is greater than the fixed frame rate (1/fixedDeltaTime) the rigidbody can't move on it's own.
Since collisions are calculated / applied after the drag, they can cause an impuls spike in velocity and movement, but the next fixed frame the velocity will be 0 again unless there are consequential collisions which again apply an impule force. Note: as long as a collision is "active" (as long as collision stay is called) there might be forces applied from the colliding rigidbody. However all momentum that got tranferred will completely vanish the next frame.
Example: If you have set your fixedDeltaTime to 0.01 you will get 100 FixedUpdates per second. If you set the drag value to 50 and initial velocity of 100 will be cut in half each fixed frame because the "percentage multiplier" is (1 - (0.01 * 50)) == 0.5
If the drag is 100 or greater the multiplier will be 0 ( == 1 - (0.01*100)). Keep in mind that the default setting for the fixedDeltaTime is 0.02 which gives you a max drag of 50
I haven't tested when and how Joints apply their forces. If they use the "normal" AddForce mechanics the drag could draw the Joint completely useless. If it's appllied internally after the drag it might have an affect. But keep in mind that no velocity will survive the next frame if your drag >= 1 / fixedDeltaTime
Conclusion: That's simply a strange approach. I'm not sure if this was different in the past, but anyways the "hint" on the drag field is clearly wrong since a range of (0 to infinity) makes no sense. The manual states the same, so i guess they *wanted* to do something like HarvesteR suggested as the second example:
Code (csharp):
velocity *= 1 / (1 + drag*dt);
but somehow they didn't.
Determined and tested using Unity 4.5.4f1
Derived from Bunny83's answer, the following script is a stand-alone approximate implementation of Unity's drag:
Code (CSharp):
using UnityEngine;
public class PhysicsDrag : MonoBehaviour
{
public float drag;
private Rigidbody Body;
private void Start()
{
Body = GetComponent<Rigidbody>();
Body.drag = 0f;
}
private void FixedUpdate()
{
Body.velocity *= Mathf.Clamp01(1f - drag * Time.fixedDeltaTime);
}
}
With just a bit more code, you can re-implement both Unity's gravity and drag, and achieve perfect results:
Code (CSharp):
using UnityEngine;
public class PhysicsGravityAndDrag : MonoBehaviour
{
public float drag;
private Rigidbody Body;
private void Start ()
{
Body = GetComponent<Rigidbody>();
Body.useGravity = false;
Body.drag = 0f;
}
private void FixedUpdate()
{
var velocity = Body.velocity;
velocity += Physics.gravity * Time.fixedDeltaTime;
velocity *= Mathf.Clamp01(1f - drag * Time.deltaTime);
Body.velocity = velocity;
}
}
Many thanks to Bunny83 for finally leading me to the solution to my own drag-calculation troubles.
相关文章推荐
- Unity基础小案例---射击小球
- unity, windows: Unhandled Exception: System.UnauthorizedAccessException: Access to the path "XXX\Temp\Assembly-CSharp.dll.mdb" is denied
- unity 右键关闭
- unity3d仿仙剑角色控制
- unity3d游戏物体跟着鼠标方向移动
- UNITY 5.2 Object类
- Unity3D 游戏的碰撞
- Unity3D 创建一个简单的2D游戏
- Unity Assets目录下的特殊文件夹名称
- Unity3d 协程、调用函数、委托
- Unity3d 协程、调用函数、委托
- 【笨木头Unity】入门之旅001:学游泳的第一步是下水
- 【笨木头Unity】入门之旅000:前言
- Unity3d Shader
- Effective Unity3D
- Unity碰撞(Collider)的深入理解
- Unity3d 动态批处理的问题
- UNITY 5.2 Transform类
- unity, 由于project settings中time scale变成0导致动画不播放
- unity版本控制工具的使用