Planet (Faux) Gravity Tutorial

This tutorial will teach you how to create Planet Gravity effect like Super Mario Galaxy in Unity3d.

Create new Unity project and add sphere in scene and name it as Planet.

Add Rigidbody on the planet game object, Disable use gravity and set it’s kinematic to true.

Add new Script called PlanetGravityAttractor on the Planet Gameobject

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


[RequireComponent(typeof(Rigidbody))]
public class PlanetGravityAttractor : MonoBehaviour
{
    public Vector3 gravityCenter = Vector3.zero; // attractor center of gravity
    public float gravitationConstant = -9.8f; // our attractor gravity

    private float attractorMass; // mass of our planet

    void Start()
    {
        attractorMass = this.GetComponent<Rigidbody>().mass;
    }

    /// <summary>
    /// Attract other body to this GameObject
    /// </summary>
    /// <param name="Body"></param>
    /// <returns></returns>
    public void Attract(Rigidbody attractedBody)
    {
        Vector3 pullVector = FindSurfaceNormal(attractedBody); // Find the surface normal of Attractor
        OrientBody(attractedBody, pullVector); // Orient the player to surface normal

        // Calculate the pull force of attractor
        float pullForce = 0.0f;
        float attractedBodyDistance = Vector3.Distance(this.transform.position + gravityCenter, attractedBody.transform.position);
        
        // Inverse square law - GravityConstant * ((mass1 * mass2) / distance^2)
        pullForce = gravitationConstant * ((attractorMass * attractedBody.mass) / Mathf.Pow(attractedBodyDistance, 2));
        
        // find direction to attractedbody
        pullVector = attractedBody.transform.position - gravityCenter;
        // apply gravitation force
        attractedBody.AddForce(pullVector.normalized * pullForce * Time.deltaTime);
    }

    /// <summary>
    /// Find the surface normal of planet
    /// used for rotation for attractedBody according to 
    /// surface of planet
    /// </summary>
    /// <param name="attractedBody"></param>
    /// <returns></returns>
    Vector3 FindSurfaceNormal(Rigidbody attractedBody)
    {
        float distance = Vector3.Distance(this.transform.position, attractedBody.position);
        Vector3 surfaceNorm = Vector3.zero;

        RaycastHit hit;
        if (Physics.Raycast(attractedBody.transform.position, -attractedBody.transform.up, out hit, distance))
        {
            surfaceNorm = hit.normal;
        }
        return surfaceNorm;
    }

    /// <summary>
    /// Orient attracted body to surface normal of attractor
    /// </summary>
    /// <param name="attractedBody"></param>
    /// <param name="surfaceNormal"></param>
    /// <returns></returns>
    void OrientBody(Rigidbody attractedBody, Vector3 surfaceNormal)
    {
        attractedBody.transform.rotation = Quaternion.FromToRotation(attractedBody.transform.up, surfaceNormal) * attractedBody.rotation;
    }
}

I am using Newton’s law of universal gravitation. You are free to use any other formula to get desired effect. The Script has 3 methods which are as follows,
1. void Attract(Rigidbody attractedBody) – It attracts given rigidbody towards planet’s center of gravity.
2. Vector3 FindSurfaceNormal(Rigidbody attractedBody) – Finds the SurfaceNormal of Planet where Rigidbody is on. A surface normal, or simply normal, to a surface at point P is a vector perpendicular to the tangent plane of the surface at P.
3. void OrientBody(Rigidbody attractedBody, Vector3 surfaceNormal) – Rotate the Player according to Surface Normal of our Planet.
Now we have planet Gravity ready, let’s create another game object to test it.

Add new Cube and name it as Player. Add Rigid body on thePlayer and disable gravity as shown below.

Now add new Script on the Player called GravityBody.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// GameObject of body this will be attract by GravityAttractor
/// </summary>
[RequireComponent(typeof(Rigidbody))]
public class GravityBody : MonoBehaviour
{
    // Gravity Attractor reference
    PlanetGravityAttractor attractor;

    // reference Rigidbody attached to game object
    Rigidbody body;

    // Start is called before the first frame update
    void Start()
    {
        attractor = GameObject.FindGameObjectWithTag("Planet").GetComponent<PlanetGravityAttractor>();
        body = GetComponent<Rigidbody>();

        body.useGravity = false;
        body.constraints = RigidbodyConstraints.FreezeRotation;
    }

    // LateUpdate is called once after update
    void LateUpdate()
    {
        if (attractor != null && body != null)
        {
            attractor.Attract(body);
        }
    }
}

In Start method find the attractor and make sure UseGravity is disabled.
We also make sure that the player’s rotation shouldn’t be affected by force.

In LateUpdate we move the Player towards Planet using Attract method of Planet.

Last thing we have to do is Add Planet tag on the Planet GameObject, so the AttractorBody can find it. Also change Planet’s RigidBody mass to 10000 and Player’s RigidBody mass to 1000.

Hit the Play button and see the magic :p.

You can download/Clone project from GitHub.

Advertisement