Coding a bouncing slime in Processing

Bouncing Slime

Learn how to code a bouncing slime using forces and the finite state machine pattern. You will learn how to split your code up into an air state and a ground state.

In the ground state the slime will wait, then launch into the air state using an impulse force. In the air state the slime will rise into the air and then start to fall due to gravity until it hits the ground.

The tutorial uses Processing and the Java programming language. Visit my Processing page if you want to learn more about what you can achieve with coding.

Slime images

First create a Processing sketch, then save it. I called my sketch ‘BouncingSlime’.

Then click on:

Sketch –> Show Sketch Folder

Create a folder called ‘data’ inside the sketch folder. Then put the three slime images below into the data folder.

Slime ball down
Slime ball down
Slime ball ground
Slime ball ground
Slime ball up
Slime ball up

Variables

Next create some variables at the top of the sketch. These variables will store the slime images which we will load in the setup function.

PImage slimeBallGround;
PImage slimeBallUp;
PImage slimeBallDown;

This image represents the image which we are currently rendering for the slime.

PImage activeImage;

Next we will create the finite state machine variables using an enum. Notice how there is a ground and an air state. We start in the ground state.

enum State {GROUND, AIR};
State state = State.GROUND;

This variable controls where the ground is on the y axis.

float groundY = 0;

These vectors represent the position of the slime, the velocity of the slime and the strength of the gravity. Notice how we make gravity a positive value on the y axis. This is because we want the slime to move downwards.

PVector position = new PVector();
PVector velocity = new PVector();
PVector gravity = new PVector(0, 0.2);

This variable records how long we have been on the ground. You will see how to use it later.

int groundTime = 0;

Setup

Next we will create the setup function.

void setup()
{
// Code goes here
}

Inside the setup function we specify the size of the sketch. I chose to make it square shaped.

size(720, 720, P3D);

Then we turn on the anti-aliasing smoothing. So shape edges will be drawn smoother.

smooth(8);

Next the texture sampling is set to nearest. This is so the pixel art scales cleanly, since we don’t want it to blur.

// Nearest texture filtering
((PGraphicsOpenGL)g).textureSampling(2);

Now we load in the images from the data folder. Then set the active image to the ground image, since the slime will start in the ground state.

slimeBallGround = loadImage("SlimeBallGround.png");
slimeBallUp = loadImage("SlimeBallUp.png");
slimeBallDown = loadImage("SlimeBallDown.png");
  
activeImage = slimeBallGround;

Finally we will set the position of the ground, the starting position of the slime, the velocity to zero and the ground time to the current time in milliseconds. This is the time measured after running the sketch.

groundY = height / 4 * 3;
position.set(width / 2, groundY);
velocity.set(0, 0);
groundTime = millis();

Ground state

Next we will program the ground state inside of it’s own function.

void UpdateGround()
{
  // Jump
  if(millis() - groundTime > 500)
  {
    state = State.AIR;
    activeImage = slimeBallUp;
    velocity.set(0, -13);
  }
}

If the time passed is greater than 500 milliseconds, then the slime will jump into the air, and we will exit the ground state. The state is set to air, the active image is set to the slime stretching vertically and looking upwards.

Then the impulse force is applied to the velocity. This large and negative number will help shoot the slime into the air when it is added to the position later.

Air state

Now we will create a function for the air state.

void UpdateAir()
{
// Code goes here
}

First we will add our gravity force to our velocity. Remember the y velocity value will be a large negative number after coming from the ground state. Whilst the gravity value is small. So over time the gravity value will make the y velocity value turn into a positive number.

// Force accumulation
velocity.add(gravity);

Whilst the velocity is negative the slime will move up. And the slime will move down when the velocity is positive.

Then we will add our velocity to our position variable.

position.add(velocity);

Next we check if we have reached the ground. This is our exit condition for the air state.

We first check if the vertical y position is greater than the ground y value. This means we have hit the ground! So we change the state to the ground state, set the position to the ground position and set the velocity to zero.

Then we set the active image to the ground image, and set the ground time variable to the current time, so we can check how long we have been on the ground later. We also return since we want to exit the function.

// Check if on ground
if(position.y > groundY)
{
  state = State.GROUND;
  position.y = groundY;
  velocity.y = 0;
  activeImage = slimeBallGround;
  groundTime = millis();
  return;
}

Finally if we are still in the air state, then we will switch the active image based on the velocity. If the velocity is negative, then we set the active image to the slime up image, since we are moving upwards. If the velocity is positive, then we set the active image to the slime down image, since we are moving downwards.

// Switch the active image based on velocity
if(velocity.y < 0)
{
  activeImage = slimeBallUp;
}
else if(velocity.y > 0)
{
  activeImage = slimeBallDown;
}

Draw function

The last thing we need to do is create the draw function.

void draw()
{
// Code goes here
}

First we will use a switch case to call either the ground state function or the air state function, depending on which state we are in. The key thing here is that we can only be in one state at a time!

// Logic
switch(state)
{
  case GROUND:
  UpdateGround();
  break;
  case AIR:
  UpdateAir();
  break;
}

Next we will write our rendering code. So we first start by clearing the screen to our sky colour. Since we will draw our ground and slime on top of it.

// Render
// Clear the screen to background sky colour
background(80, 140, 200);

Then we draw the ground as a green rectangle.

// Draw the ground
fill(68, 160, 80);
rect(0, groundY, width, groundY);

Finally we will draw the active slime image from it’s centre. Hence why we offset it’s position using half it’s size.

// Draw the slime ball
noStroke();
noFill();
final int w = activeImage.width;
final int h = activeImage.height;
image(activeImage, position.x - w / 2, position.y - h / 2);

Conclusion

Congratulations on completing this tutorial! I hope you enjoyed it, and learned some cool stuff about forces and the state machine pattern.

For more finite state machine tutorials, check out:

  1. Rabbit and Carrot Finite State Machine
  2. Three simple ways to code finite state machines

Leave a comment