Limiting a rigidbody’s velocity is a problem I’ve tried to solve in Unity over and over again with this trick:
rigidbody.velocity = Vector3.ClampMagnitude( rigidbody.velocity, terminalVelocity );
The problem with this method is that by setting the velocity in every frame, adding forces to the rigidbody can’t have any effect. In general, using this trick has always led to strange things happening. There must be a better solution.
Recently I remembered my high school physics lessons about how objects have a terminal velocity – a velocity they cannot move quicker than. This occurs when drag (a force proportionate to the object’s velocity) cancels out the constant force on the body (such as gravity). I realised this could be used with Unity’s built in drag to get a very simple solution for limiting velocity on bodies with a constant force applied.
So first I had to make an assumption about Unity’s drag equation. I guessed at this:
Vector3 dragForce = -rigidbody.velocity * rigidbody.drag
Terminal velocity occurs when the constant force and the drag force are equal, so this can be rearranged to find the drag required for a particular terminal velocity.
rigidbody.drag = boostForceMagnitude / terminalVelocity;
Simple right? Actually, it turns out that the drag equations can’t be what I thought. I did some tests with this method and it turned out that the limiting velocity was always exactly 0.2 less than what I wanted, no matter what the boostForceMagnitude or terminalVelocity was… So a bit of fiddling and it turned out that drag must have some sort of dependency on the length of the time step. I also tried to emulate the ConstantForce component in FixedUpdate and interestingly it turns out that it doesn’t scale the force by Time.fixedDeltaTime … So that means if you use this component (or indeed drag), changing your time step will have side-effects on your physics.
rigidbody.drag = boostForceMagnitude/ ( terminalVelocity + 10f*Time.fixedDeltaTime );
The full ‘Boost’ class:
using UnityEngine;
using System.Collections;
public class Boost : MonoBehaviour {
public float acceleration, terminalVelocity;
// Use this for initialization
void Start () {
float boostForceMagnitude = rigidbody.mass*acceleration;
rigidbody.drag = boostForceMagnitude/(terminalVelocity+10f*Time.fixedDeltaTime);
ConstantForce f = gameObject.AddComponent();
f.relativeForce = Vector3.forward*boostForceMagnitude;
}
void OnGUI() {
GUI.Label( new Rect(0,0,200,20), "Speed: " + rigidbody.velocity.magnitude.ToString("N4") );
}
}
This implementation will only limit velocity when you have a constant force on your body. Soon I’ll try to work out a generic solution to clamping velocity within a certain range just with drag. I wonder if there is an analytical solution using momentum or something…