Just thought I’d necro this thread to clarify some things because I hit a similar snag and ended up here via google (with 4,000+ views, seems I’m not the only one).
I tried Factoid’s formula and it appeared to work (Yay!), but that didn’t really make sense to me because the math is wrong (WTF be happy, it works!). I don’t like it when things don’t make sense so I did some digging. The reason why Factoid’s formula works is because in Unity forces are applied to the rigidbody then drag is calculated and applied afterwards, which I think might actually be a bug.
I eventually discovered that the PhysX rigidbody’s drag property seems to be used to apply a basic Stokes drag which in Unity you’d perhaps integrate simply as:
velocity += (forces - drag*velocity) * dt;
However, with Unity, forces appear to be applied before this drag caculation (not sure why?) so it actually ends up being this:
velocity += forces * dt;
velocity -= drag*velocity * dt;
Which still feels fine in game but the drag is being calculated with the wrong velocity.
This also explains why Factoid’s method seems to work. It’s still not correct, but it’s close enough with constant acceleration that it really doesn’t matter. However, the best way to do this is to set rigidbody.velocity directly, do your own drag calculation and always set a rigid body’s drag to zero. This way you can use the more correct math and still get the desired result:
// a = acceleration, forward = transform.forward, dt = fixedDeltaTime
var drag = a / maxSpeed;
rigidbody.velocity += ((forward * a) - (rigidbody.velocity * drag)) * dt;
As proof, here’s two monobehaviours you can easily setup and test, one uses PhysX (will need rigid body), the other just does manual Euler integration in fixed update - both move identically.
PhysX:
using UnityEngine;
using System.Collections;
public class PhysXTest : MonoBehaviour {
public float topSpeed = 10f;
public float drag = 0f;
public float acceleration = 10f;
public float currentSpeed;
void FixedUpdate()
{
drag = (acceleration / topSpeed);
rigidbody.velocity += (Vector3.right * acceleration - (drag * rigidbody.velocity)) * Time.fixedDeltaTime;
currentSpeed = rigidbody.velocity.magnitude;
}
}
Simple Euler:
using UnityEngine;
using System.Collections;
public class PhysicsTest : MonoBehaviour
{
public float topSpeed = 10f;
public float drag = 0f;
public float acceleration = 10f;
private Vector3 velocity;
public float currentSpeed;
void FixedUpdate()
{
drag = (acceleration / topSpeed);
velocity += (Vector3.right * acceleration - (drag * velocity)) * Time.fixedDeltaTime;
transform.position += velocity * Time.fixedDeltaTime;
currentSpeed = velocity.magnitude;
}
}
I also tested this by integrating the drag incorrectly in the Euler version of the above and setting the drag property on the rigidbody like so:
// Manual
var drag = (acceleration / topSpeed);
velocity += Vector3.right * acceleration * Time.fixedDeltaTime;
velocity -= drag * velocity * Time.fixedDeltaTime;
//PhysX
rigidbody.drag = (acceleration / topSpeed);
rigidbody.AddForce(Vector3.right * acceleration);
Even after letting it run for several minutes Unity’s PhysX was identical Step for Step so this is probably a bug in the PhysX implementation of Unity.
Note: This all assumes you’re using objects with a mass of 1. If not you’ll need to integrate that into the equations too.