Unity Resource: VelocityLimiter

Unity LogoOne of the cool things about Unity3D is its built-in physics engine. Instead of having to manually code interactions between objects, gravitational acceleration, etc., in many cases you can just specify an object’s mass, drag, and so on, and let the physics engine do the rest.

However, when working with non-kinematic rigidbodies (i.e. objects controlled by the physics engine), there can be situations where collisions with static colliders and other rigidbodies result in large increases in velocity. This can lead to undesirable effects, such as objects suddenly “teleporting” to random parts of the screen or breaking through static collider scenery.

I solved this problem in Intergalactic Sheep Pong by creating a script to limit the velocity of a rigidbody. Eventually I developed two flavors of it: SimpleVelocityLimiter, which clips the velocity if it exceeds a specified maximum, and VelocityLimiter, which increases the object’s drag once it crosses a given threshold, and clips the velocity only if it goes above a secondary threshold. I found that the second approach (using drag first and clipping only when necessary) seemed to result in more natural-feeling behavior. (I also suspect it might be more efficient, but I haven’t run timings on it.)

Here’s an example of the velocity limiters in action. (Click here to download the project zip.) There are three balls in a box. Each one has a rigidbody component attached to it, they are all “bouncy,” and they all have a drag of zero. Whenever a ball strikes the floor, a small upward force is applied to it. You can also move the ceiling up and down with the arrow keys. The balls are identical in every way, except:

  • The first ball is just a plain rigidbody.
  • The second ball has a SimpleVelocityLimiter attached to it.
  • The third ball has a VelocityLimiter attached to it.

Let the example run for a bit, and the first ball will eventually smash its way out of the box. The other balls, however, will not, even when the ceiling is lowered onto them.

EDIT: See also Vector3.ClampMagnitude and Rigidbody.collisionDetectionMode, both of which are new in Unity 3.

Here’s the source code for the two limiter scripts. As usual, feel free to use/tweak/modify them.

SimpleVelocityLimiter.js

#pragma strict

// This MonoBehaviour uses hard clamping to limit the velocity of a rigidbody.

// The maximum allowed velocity. The velocity will be clamped to keep
// it from exceeding this value.
var maxVelocity : float;

// The cached rigidbody reference.
private var rb : Rigidbody;
// A cached copy of the squared max velocity. Used in FixedUpdate.
private var sqrMaxVelocity : float;

// Awake is a built-in unity function that is called called only once during the lifetime of the script instance.
// It is called after all objects are initialized.
// For more info, see:
// http://unity3d.com/support/documentation/ScriptReference/MonoBehaviour.Awake.html
function Awake() {
	rb = rigidbody;
	SetMaxVelocity(maxVelocity);
}

// Sets the max velocity and calculates the squared max velocity for use in FixedUpdate.
// Outside callers who wish to modify the max velocity should use this function. Otherwise,
// the cached squared velocity will not be recalculated.
function SetMaxVelocity(maxVelocity : float){
	this.maxVelocity = maxVelocity;
	sqrMaxVelocity = maxVelocity * maxVelocity;
}

// FixedUpdate is a built-in unity function that is called every fixed framerate frame.
// We use FixedUpdate instead of Update here because the docs recommend doing so when
// dealing with rigidbodies.
// For more info, see:
// http://unity3d.com/support/documentation/ScriptReference/MonoBehaviour.FixedUpdate.html
function FixedUpdate() {
	var v = rb.velocity;
	// Clamp the velocity, if necessary
	// Use sqrMagnitude instead of magnitude for performance reasons.
	if(v.sqrMagnitude > sqrMaxVelocity){ // Equivalent to: rigidbody.velocity.magnitude > maxVelocity, but faster.
		// Vector3.normalized returns this vector with a magnitude
		// of 1. This ensures that we're not messing with the
		// direction of the vector, only its magnitude.
		rb.velocity = v.normalized * maxVelocity;
	}
}

// Require a Rigidbody component to be attached to the same GameObject.
@script RequireComponent(Rigidbody)

VelocityLimiter.js

#pragma strict

// This MonoBehaviour uses drag as well as hard clamping to limit the velocity of a rigidbody.

// The velocity at which drag should begin being applied.
var dragStartVelocity : float;
// The velocity at which drag should equal maxDrag.
var dragMaxVelocity : float;

// The maximum allowed velocity. The velocity will be clamped to keep
// it from exceeding this value. (Note: this value should be greater than
// or equal to dragMaxVelocity.)
var maxVelocity : float;

// The maximum drag to apply. This is the value that will
// be applied if the velocity is equal or greater
// than dragMaxVelocity. Between the start and max velocities,
// the drag applied will go from 0 to maxDrag, increasing
// the closer the velocity gets to dragMaxVelocity.
var maxDrag : float = 1.0;

// The original drag of the object, which we use if the velocity
// is below dragStartVelocity.
private var originalDrag : float;
// Cache the rigidbody to avoid GetComponent calls behind the scenes.
private var rb : Rigidbody;
// Cached values used in FixedUpdate
private var sqrDragStartVelocity : float;
private var sqrDragVelocityRange : float;
private var sqrMaxVelocity : float;

// Awake is called when the script instance is being loaded.
// For more info, see:
// http://unity3d.com/support/documentation/ScriptReference/MonoBehaviour.Awake.html
function Awake(){
	originalDrag = rigidbody.drag;
	rb = rigidbody;
	Initialize(dragStartVelocity, dragMaxVelocity, maxVelocity, maxDrag);
}

// Sets the threshold values and calculates cached variables used in FixedUpdate.
// Outside callers who wish to modify the thresholds should use this function. Otherwise,
// the cached values will not be recalculated.
function Initialize(dragStartVelocity : float, dragMaxVelocity : float, maxVelocity : float, maxDrag : float){
	this.dragStartVelocity = dragStartVelocity;
	this.dragMaxVelocity = dragMaxVelocity;
	this.maxVelocity = maxVelocity;
	this.maxDrag = maxDrag;

	// Calculate cached values
	sqrDragStartVelocity = dragStartVelocity * dragStartVelocity;
	sqrDragVelocityRange = (dragMaxVelocity * dragMaxVelocity) - sqrDragStartVelocity;
	sqrMaxVelocity = maxVelocity * maxVelocity;
}

// FixedUpdate is a built-in unity function that is called every fixed framerate frame.
// We use FixedUpdate instead of Update here because the docs recommend doing so when
// dealing with rigidbodies.
// For more info, see:
// http://unity3d.com/support/documentation/ScriptReference/MonoBehaviour.FixedUpdate.html
//
// We limit the velocity here to account for gravity and to allow the drag to be relaxed
// over time, even if no collisions are occurring.
function FixedUpdate(){
	var v = rb.velocity;
	// We use sqrMagnitude instead of magnitude for performance reasons.
	var vSqr = v.sqrMagnitude;

	if(vSqr > sqrDragStartVelocity){
		rigidbody.drag = Mathf.Lerp(originalDrag, maxDrag, Mathf.Clamp01((vSqr - sqrDragStartVelocity)/sqrDragVelocityRange));

		// Clamp the velocity, if necessary
		if(vSqr > sqrMaxVelocity){
			// Vector3.normalized returns this vector with a magnitude
			// of 1. This ensures that we're not messing with the
			// direction of the vector, only its magnitude.
			rb.velocity = v.normalized * maxVelocity;
		}
	} else {
		rb.drag = originalDrag;
	}
}

// Require a Rigidbody component to be attached to the same GameObject.
@script RequireComponent(Rigidbody)
Advertisements

7 thoughts on “Unity Resource: VelocityLimiter

  1. Great script! Thanks! Besides needing a velocity limiter, I had been recently wondering out to use sqrMagnitude instead of magnitude which this explains! thanks again!

  2. i like it very clever i was wandering about the natural behaviour aspect of it myself and decided to multiply a rigidbodies actual velocity by a fraction when it reached a certain threshold but using drag seems easier smarter.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s