Tweakable Gravity Script

Having a bit of free time, I’ve been starting to play with Unity’s physics for the first time. The first thing I wanted to try was a gravity script to model planets and such.

I saw after I finished my script that there’s already one on the wiki, but noticed that mine has a few differences for anyone seeking a bit more control of the mechanics.

For example, with this script you can easily set the falloff curve or gravitational constant, which might not be realistic, but if you want to make a fast paced gravity-slingshotting racing game, for example, realism can take a hike!

Anyway, enjoy! (Sorry, no javascript version at the moment…)

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Collider))]
[RequireComponent(typeof(Rigidbody))]

public class Gravity : MonoBehaviour 
{
	//The minimum force that will be exerted by this object's gravity on an object of identical mass.
	//Below this value we'll call the force negligible and won't bother calculating it.
	public float gravityMin;
	
	//Static floor property to globally limit influence radii for performance.
	private static float gravFloor;
	public static float gravityFloor
	{
		get { return gravFloor;}
		set { gravFloor = value;}
	}
	
	//The range of influence of this object's gravity as calculated using gravityMin.
	private float gravRadius;
	
	//Gravity falls of with the square of distance, so to be physically accurate this should be set to 2.
	//As this value approaches 0 gravity will approach constant (i.e. will not fall off with distance).  
	//Negative values do very strange things.
	private static float gravFalloff;
	public static float gravityFalloff
	{
		get { return gravFalloff;}
		set 
		{ 
			if (value < 0.1f) { gravFalloff = 0.1f;}
			else { gravFalloff = value;}
		}
	}
	
	//The Gravitational Constant.  In reality this value would be 6.67*10^-11, but that makes gravity very weak except for very large masses.
	//Increasing this value simulates the stronger gravitation between huge objects (i.e. planets) using saner numbers for the rigidbody masses.
	//Negative values will cause gravity to repulse rather than attract objects, 0 will nullify gravity (making all this calculation useless!).
	private static float gravConst;
	public static float gravityConstant
	{ 
		get { return gravConst;}
		set { gravConst = value;}
	}
	
	//Objects inside this object's range of influence.
	private Rigidbody[] bodies;
	
	public void Start() 
	{
		SetInfluenceRadius();
	}
	
	public void FixedUpdate() 
	{
		FindRigidbodies();
		ApplyGravity();
	}
	
	public void SetInfluenceRadius() //Calculates the distance at which the force of this object's gravity on an object of the same mass will fall off to gravityMin.  
	{
		if (gravityMin < gravFloor) { gravityMin = gravFloor;}
		
		gravRadius = Mathf.Pow(Mathf.Abs(gravConst / gravityMin), 1f / gravFalloff) * rigidbody.mass;
	}
	
	public void SetInfluenceRadius(float newMin) //Public override to more conveniently update influence radius.  
	{
		gravityMin = newMin;
		
		if (gravityMin < gravFloor) { gravityMin = gravFloor;}
		
		gravRadius = Mathf.Pow(Mathf.Abs(gravConst / gravityMin), 1f / gravFalloff) * rigidbody.mass;
	}
	
	private void FindRigidbodies()
	{
		//Find all colliders inside this object's range of influence.
		Collider[] tempCollider = Physics.OverlapSphere(transform.position, gravRadius);
		
		//Sort the returned array for any rigidbodies attached to colliders, excluding this object.
		ArrayList tempBodies = new ArrayList();
		foreach (Collider coll in tempCollider)
		{
			if ( coll != collider  coll.rigidbody != null) { tempBodies.Add(coll.rigidbody);}
		}
		
		bodies = (Rigidbody[])tempBodies.ToArray(typeof(Rigidbody));
	}
	
	private void ApplyGravity() //Applies the force of this object's gravity to all objects inside its range of influence.
	{
		foreach (Rigidbody body in bodies)
		{
			//Calculate the force multiplier for gravity between the two bodies using the gravitation formula F = G * m1 * m2 / r^f where f is our variable falloff factor.
			float gravForce = gravConst * rigidbody.mass * body.mass / Mathf.Pow(Vector3.Distance(transform.position, body.transform.position), gravFalloff);
			//Find the force vector as the difference between positions of the two bodies .
			Vector3 gravDirection = transform.position - body.transform.position;
			//Apply gravity as the force vector (as a unit vector) times the force multiplier.
			body.AddForce(gravDirection.normalized * gravForce);
		}
	}
	
	public void OnDrawGizmos() //Draws the range of influence of this object's gravity.
	{
		Gizmos.color = Color.green;
		Gizmos.DrawWireSphere(transform.position, gravRadius);
	}
}

Edit: Added static gravityFloor property as mentioned in my post below.

Nice work! Looks like it gives plenty of options for how you want gravity to behave. I’ve been thinking about adding gravity to one of my games and this looks like the perfect way.

How’s the performance?

Thanks! I haven’t thoroughly stress-tested the script yet, but so far it’s been fine with anything I’ve thrown at it. I haven’t tried hundreds of objects, for example, but with a few dozen it seems to run along nicely.

My guess, though, is that performance might take a small hit compared to the wiki script due to a few extra calculations, but the way the influence radius is calculated should prevent it from getting out of control. If you have a lot of objects attracting each other and it starts getting bogged down you could always increase the gravityMin properties to ease the load a bit.

Actually, that’s an idea. I should add a static floor value for gravityMin to make it easier to globally tweak that for performance.