# 理解Unity3d的ForceMode | Understanding ForceMode in Unity3D

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() {
}

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;
}

}

}


ForceMode.VelocityChange does exactly what the name suggests. So those two lines do exactly the same:

1.  // The passed "force" parameter is in m/s which is added instantly to the velocity
2.  rigidbody.AddForce(Vector3.forward*1.0f,ForceMode.VelocityChange);
3.  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:

1.  // The passed "force" is in m/s². If applied every fixed frame the accumulated velocity will increased by "force" m/s every second
2.  rigidbody.AddForce(Vector3.forward*1.0f,ForceMode.Acceleration);
3.  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:

1.  // "force" is in newton (kg*m/s²)
2.  rigidbody.AddForce(Vector3.forward*1.0f,ForceMode.Force);
3.  rigidbody.velocity += Vector3.forward*1.0f * Time.fixedDeltaTime / (rigidbody.mass);

ForceMode.Impulse:

1.  // "force" is a impulse / momentum which is in kg*m/s
2.  rigidbody.AddForce(Vector3.forward*1.0f,ForceMode.Impulse);
3.  rigidbody.velocity += Vector3.forward*1.0f / rigidbody.mass;

edit
Two quick crosslinks to how drag is applied:

edit

1.  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.

1.  //C#
2.  float GetFinalVelocity(float aVelocityChange, float aDrag)
3.  {
4.  return aVelocityChange * (1 / Mathf.Clamp01(aDrag * Time.fixedDeltaTime) - 1);
5.  }
6.  float GetFinalVelocityFromAcceleration(float aAcceleration, float aDrag)
7.  {
8.  return GetFinalVelocity(aAcceleration * Time.fixedDeltaTime, aDrag);
9.  }
10.  float GetDrag(float aVelocityChange, float aFinalVelocity)
11.  {
12.  return aVelocityChange / ((aFinalVelocity + aVelocityChange) * Time.fixedDeltaTime);
13.  }
14.  float GetDragFromAcceleration(float aAcceleration, float aFinalVelocity)
15.  {
16.  return GetDrag(aAcceleration * Time.fixedDeltaTime, aFinalVelocity);
17.  }
18.  float GetRequiredVelocityChange(float aFinalSpeed, float aDrag)
19.  {
20.  float m = Mathf.Clamp01(aDrag * Time.fixedDeltaTime);
21.  return aFinalSpeed * m / (1 - m);
22.  }
23.  float GetRequiredAcceleraton(float aFinalSpeed, float aDrag)
24.  {
25.  return GetRequiredVelocityChange(aFinalSpeed, aDrag) / Time.fixedDeltaTime;
26.  }

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):
1.
2. CallFixedUpdate();
3. velocity = ApplyForces(velocity);
4. velocity *= Mathf.Clamp01(1-drag * dt);
5. velocity = ApplyCollisionForces(velocity);
6. position += velocity * dt;
7.

• "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):
1.
2. velocity *= 1 / (1 + drag*dt);
3.

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):
1. using UnityEngine;
2.
3. public class PhysicsDrag : MonoBehaviour
4. {
5.     public float drag;
6.
7.     private Rigidbody Body;
8.
9.     private void Start()
10.     {
11.         Body = GetComponent<Rigidbody>();
12.
13.         Body.drag = 0f;
14.     }
15.
16.     private void FixedUpdate()
17.     {
18.         Body.velocity *= Mathf.Clamp01(1f - drag * Time.fixedDeltaTime);
19.     }
20. }

With just a bit more code, you can re-implement both Unity's gravity and drag, and achieve perfect results:

Code (CSharp):
1. using UnityEngine;
2.
3. public class PhysicsGravityAndDrag : MonoBehaviour
4. {
5.     public float drag;
6.
7.     private Rigidbody Body;
8.
9.     private void Start ()
10.     {
11.         Body = GetComponent<Rigidbody>();
12.
13.         Body.useGravity = false;
14.         Body.drag = 0f;
15.     }
16.
17.     private void FixedUpdate()
18.     {
19.         var velocity = Body.velocity;
20.
21.
22.         velocity *= Mathf.Clamp01(1f - drag * Time.deltaTime);
23.
24.         Body.velocity = velocity;
25.     }
26. }

Many thanks to Bunny83 for finally leading me to the solution to my own drag-calculation troubles.

posted @ 2015-10-18 22:52 O和尚O 阅读(...) 评论(...) 编辑 收藏