Coding Tutorial: 3D Gravity

3d gravity - planet jumper - image

Learn how to code 3D gravity in this coding tutorial using vectors and forces to move an object around a planet and jump between planets.

Introduction

The tutorial will use Processing and the Java programming language. So if you have knowledge of any curly brace language, then you should be fine to follow along to this tutorial. If you wish to code the tutorial for yourself, and are not familiar with Processing, then check out this tutorial to get started.

Circle

Open Processing, then create a new tab called ‘Circle’. We will create a Circle class, which we will use for collision detection between the player object and the planets.

class Circle
{
// Code goes here
}

Next we will add some variables and functions inside of the Circle class.

Create variables for the position and radius of the circle.

public float x = 0, y = 0, radius = 1;

Then create three set functions, which will set the position and radius.

  void SetPosition(float x, float y)
  {
    this.x = x;
    this.y = y;
  }
  
  void SetRadius(float radius)
  {
    this.radius = radius;
  }
  
  void Set(float x, float y, float radius)
  {
    SetPosition(x, y);
    SetRadius(radius);
  }

Now create two constructors. A default one, and another which takes arguments.

  Circle()
  {
    Set(0, 0, 1);
  }
  
  Circle(float x, float y, float radius)
  {
    Set(x, y, radius);
  }

Finally we just need a function which checks for an overlap between two circles. The function uses the squared radii of the two circles and the squared delta between the positions to check for a collision.

  boolean Overlaps(Circle other)
  {
    final float dx = this.x - other.x;
    final float dy = this.y - other.y;
    final float r = this.radius+other.radius;
    return dx*dx+dy*dy <= r*r;
  }

Planet

Next we will code the Planet class. Create a new tab called ‘Planet’. Then create a Planet class.

class Planet
{
// Code goes here
}

Now we will add some data members and functions inside of the planet class.

First create a circle data member. This will represent both the position and size of the planet.

private Circle circle = new Circle();

Next create three colour variables which will be used to set the fill of the planet.

private float red = 0, green = 0, blue = 0;

Create a render function, which will render the planet as a circle. The stroke is represented as a darker colour than the fill, since it is multiplied by a number less than one.

  void Render()
  {
    stroke(red * 0.35, green * 0.35, blue * 0.35);
    strokeWeight(1);
    fill(red, green, blue);
    circle(circle.x, circle.y, circle.radius*2);
  }

Next create some functions for getting and setting the position of the circle.

  float GetX()
  {
    return circle.x;
  }
  
  float GetY()
  {
    return circle.y;
  }
  
  void SetPosition(float x, float y)
  {
    circle.SetPosition(x, y);
  }

And we also need functions for getting and setting the radius of the circle.

  float GetRadius()
  {
    return circle.radius;
  }
  
  void SetRadius(float radius)
  {
    circle.SetRadius(radius);
  }

Now add an overlaps function, which checks for overlap with the planet’s circle.

  boolean Overlaps(Circle other)
  {
    return circle.Overlaps(other);
  }

Finally add one more function, which will be used to set the fill colour.

  void SetFill(float red, float green, float blue)
  {
    this.red = red;
    this.green = green;
    this.blue = blue;
  }

Player Input

Next we need a class which will store the player input variables, every time the keyboard is pressed and released.

Create a new tab called ‘PlayerInput’. Then create a class with the same name.

class PlayerInput
{
// Code goes here
}

Create three Boolean variables, which will store the direction the player wants to move in, and whether they want to jump or not.

  boolean left = false;
  boolean right = false;
  boolean jump = false;

Next set the a variable to true if the chosen key is pressed.

  void OnKeyPress(char key)
  {
    if('a' == key) left = true;
    if('d' == key) right = true;
    
    if(' ' == key) jump = true;
  }

Set a variable to false if the chosen key is released.

  void OnKeyRelease(char key)
  {
    if('a' == key) left = false;
    if('d' == key) right = false;
    
    if(' ' == key) jump = false;
  }

Main Tab

Go back to the main tab, then create a variable to represent the strength of gravity.

final float GRAVITY_STRENGTH = 0.5f;

Next create a ‘PlayerInput’ instance.

PlayerInput playerInput = new PlayerInput();

Now override the Processing ‘keyPressed’ and ‘keyReleased’ functions, and call the respective ‘playerInput’ functions.

void keyPressed()
{
  playerInput.OnKeyPress(key);
}

void keyReleased()
{
  playerInput.OnKeyRelease(key);
}

Player

Create a new tab called ‘Player’. Then create a class with the same name.

class Player
{
// Code goes here
}

Next we will add some data members and functions inside of the player class.

First create a circle data member.

private Circle circle = new Circle();

Now create some variables for moving the player, and jumping.

  private PVector force = new PVector();
  private PVector velocity = new PVector();
  private float mass = 1.0f;
  private float moveForce = 4.0f;
  private float jumpForce = 128.0f;
  private boolean onPlanet = false;

Create two functions for setting the player position and radius.

  void SetPosition(float x, float y)
  {
    circle.SetPosition(x, y);
  }
  
  void SetRadius(float radius)
  {
    circle.SetRadius(radius);
  }

Then create a render function, which will render the player object as a circle.

  void Render()
  {
    stroke(0);
    strokeWeight(1);
    fill(10, 220, 20);
    circle(circle.x, circle.y, circle.radius*2);
  }

Create a new function which will apply a force. The force variable is used to accumulate forces.

  void ApplyForce(float x, float y)
  {
    if(mass > 0.0f)
    {
      force.add(x / mass, y / mass);
    }
  }

Next we need a function for finding the nearest planet (if there is one). So essentially we will loop through all the planets and compare the squared magnitude of the delta positions between the player and each planet. The one with the smallest squared magnitude is the nearest.

  Planet FindNearestPlanet()
  {
    final int size = planetList.size();
    if(size < 1) return null;
    
    Planet nearest = planetList.get(0);
    PVector delta = new PVector();
    delta.set(circle.x, circle.y);
    delta.sub(nearest.GetX(), nearest.GetY());
    float magSquared = delta.magSq();
    
    Planet current = null;
    float currentMagSquared = 0;
    for(int i=1; i<size; ++i)
    {
      current = planetList.get(i);
      delta.set(circle.x, circle.y);
      delta.sub(current.GetX(), current.GetY());
      currentMagSquared = delta.magSq();
      if(currentMagSquared < magSquared)
      {
        magSquared = currentMagSquared;
        nearest = current;
      }
    }
    return nearest;
  }

Finally we just need a ‘Update’ function for the player, which run all the player game logic.

  void Update()
  {
  // Code goes here
  }

Now we will code the ‘Update’ function, so put all the code inside the function.

First we find the nearest planet. If it doesn’t exist, then we will exit the function.

    Planet nearestPlanet = FindNearestPlanet();
    if(null == nearestPlanet)
    {
      println("Warning: There are no planets!");
      return;
    }

Next we will calculate the direction to the nearest planet.

    // Calculate the direction to the nearest planet
    PVector delta = new PVector();
    delta.set(nearestPlanet.GetX(), nearestPlanet.GetY());
    delta.sub(circle.x, circle.y);
    delta.normalize();

Then apply a force towards the nearest planet. This is our 3D gravity force.

    // Apply force in the direction of the nearest planet
    ApplyForce(delta.x * GRAVITY_STRENGTH, delta.y * GRAVITY_STRENGTH);

Now we will move left or right, if the respective keys are pressed by applying a force in that direction. We can calculate a vector which is 90 degrees to the left or right of the player by swapping and flipping the vector values. This is the equivalent of the cross product in 2D.

    // Move left or right
    if(playerInput.left)
    {
      ApplyForce(-delta.y * moveForce, delta.x * moveForce);
    }
    if(playerInput.right)
    {
      ApplyForce(delta.y * moveForce, -delta.x * moveForce);
    }

Then we will try jump if the correct key is pressed, and we are on the planet’s surface. We need to use a large impulse force, since we only jump in a single frame.

    // Jump
    boolean jump = false;
    if(onPlanet && playerInput.jump)
    {
      jump = true;
      onPlanet = false;
      ApplyForce(-delta.x * jumpForce, -delta.y * jumpForce);
    }

Next we add our accumulated forces to our velocity variable. Then we will limit the velocity if we are not jumping. If we didn’t check for jumping, then our impulse force wouldn’t do much. Finally we reset our force variable to zero. This is important, since all the forces accumulate in a single frame, they don’t get reused in the next frame since there values can change.

    // Add force to velocity
    velocity.add(force);
    // Limit the magnitude of the velocity if not jumping
    if(!jump) velocity.limit(10.0f);
    force.set(0.0f, 0.0f);

Now we will apply friction, which slows the player down by acting in the opposite direction to the velocity. This is mostly used to slow down the player when moving left and right.

    // Friction
    delta.set(velocity);
    delta.mult(-1);
    final float frictionStrength = 0.1f;
    velocity.add(delta.x * frictionStrength, delta.y * frictionStrength);

Then we will move the player, by adding the velocity to the player position.

    // Move to the new position
    circle.x += velocity.x;
    circle.y += velocity.y;

Finally we need to check if we have collided with the planet. If we have overlapped the planet’s circle, then we first set the ‘onPlanet’ variable to true, to say we are on the planet’s surface.

Next we will calculate a normalised vector towards the player’s position, away from the nearest planet. We use the radius of the player and the planet to put the player onto the surface of the planet.

Finally we will dampen the velocity vector, which will create a little bounce when we land.

    // Collision checking
    if(nearestPlanet.Overlaps(circle))
    {
      onPlanet = true;
      
      delta.set(circle.x, circle.y);
      delta.sub(nearestPlanet.GetX(), nearestPlanet.GetY());
      delta.normalize();
      
      final float combinedRadius = circle.radius + nearestPlanet.GetRadius();
      circle.x = nearestPlanet.GetX() + delta.x * combinedRadius;
      circle.y = nearestPlanet.GetY() + delta.y * combinedRadius;
      
      // Dampen and reverse velocity
      velocity.mult(-0.35f);
    }

Main Tab

Next we will create the player and planets. Go back to the main tab, then create a player instance.

Player player = new Player();

Then create a list of planets.

ArrayList<Planet> planetList = new ArrayList<Planet>();

Next create a function which creates a planet. The function takes a position, radius, and a fill colour.

void CreatePlanet(float x, float y, float radius, float red, float green, float blue)
{
  Planet planet = new Planet();
  planet.SetPosition(x, y);
  planet.SetRadius(radius);
  planet.SetFill(red, green, blue);
  planetList.add(planet);
}

Now create a ‘setup’ function. We set the window size, then frame rate. The frame rate controls the game speed. So if you change this value, you will have to rebalance gravity, player movement speed, etc.

Then we set the player position and radius. Next we create two planets of different sizes.

void setup()
{
  size(1280, 720);
  frameRate(120);
  
  player.SetPosition(width / 2 - width * 0.2, height / 4);
  player.SetRadius(24);
  
  CreatePlanet(width / 2 - width * 0.2, height / 2, 112, 30, 90, 180);
  CreatePlanet(width / 2 + width * 0.2, height / 2, 160, 180, 30, 90);
}

Next we just need to create a ‘draw’ function. First we call the player update function. Then we clear the screen colour. Next we render all the planets, then render the player.

void draw()
{
  // Update
  player.Update();
  
  // Render
  background(200);
  
  for(Planet planet : planetList)
  {
    planet.Render();
  }
  
  player.Render();
}

Finally all we need to do is run the sketch. You should see two planets, and a player. The player will move towards the nearest planet. You can use the ‘A’ and ‘D’ keys to move the player. And the space key to make the player jump.

3d gravity - planet jumper - shape
Jumping around planets

Conclusion

Thanks for following this tutorial, you have learned how to code 3D gravity using forces and vectors to move an object around a planet and jump between planets.

For more Processing coding tutorials, check out my Processing page here.

Leave a comment