Introduction: Pong With Processing

About: A prototyping enthusiastic with main competence in building rapid electronics prototypes.

This is a step by step guide to programming simple Pong game with Processing. Information and installation instructions for Processing can be found from https://processing.org/.

Below you can download the full code (without the Arduino bonus part).

Attachments

Step 1: Setup() and Draw(), Basic Operation of Processing Code

The basic processing sketch is composed of two functions, setup() and draw(). Like in Arduino programming, the setup() is called once in the start of the execution, and draw() is called repeatedly during the execution. by default, the draw is called 60 times per second.

To have a window for our game, we need to specify the size of the window. This is done by calling the size(), where width and height can be set to the desired dimensions. The background color of the window can be set with background() function.

void setup(){
  size(800,600);
}

void draw(){
  background(0); // Clear the window
}

Step 2: Coordinates in Processing

At this point it may be good to introduce how coordinates are defined in processing. You may be familiar with the coordinates in a typical geometry exercise, where origin is defined to be in the middle, and the positive x-axis expands to right and positive y-axis expands up. However, in Processing (and in graphical programming in general), the origin is in the top left corner, and the x-axis expand to right, and y-axis expand down. Thus we can express the top left corner coordinates as (0, 0), top right as (width, 0), where width is the width of the window, bottom left as (0, height), where height is the height of the window, and bottom right as (width,height).

Step 3: Ball, Object Oriented Programming

Explained here is the basic concept of an object and a class in Processing language. More detailed (and longer) introduction can be found from the Processing website

To start programming the Pong-game we will define the ball. In processing we can create the ball as an object. In programming, object is a collection of variables and methods. In more practical way of thinking, object in programming can be thought to be like any other object in real world. For example, a ball can have a diameter, weight, color, location in space, velocity, bounciness, and its methods could be for example rolling and bouncing. For our game we only need the location (as x and y coordinates), speed (as x and y components), diameter, and color.

To create an object, we need to have a template for creating it. In processing this is called Class. A class is the description of the object and contains the variables and functions included in the object. In a sense the relationship between class and object can be seen so, that the class is the drawings and plans of a house, and the object is the completed house in which one can live.

The class will also include a special function called constructor, which allows us to create objects from the class. With our ball we will take the location and the diameter as variables to the constructor, so they can be defined when the ball object is created. The speed is set to default zero and color to white (225 as grey scale). These parameters can be altered, after the ball object has been created from the ball class.

class Ball {
  float x;
  float y;
  float speedX;
  float speedY;
  float diameter;
  color c;
  
  // Constructor method
  Ball(float tempX, float tempY, float tempDiameter) {
    x = tempX;
    y = tempY;
    diameter = tempDiameter;
    speedX = 0;
    speedY = 0;
    c = (225); 
  }
}

To be able to do things with the ball, we need to define some methods for it. In our case we need to be able to move it, and draw it to the window. For this we define the move and display functions. The move function takes the current location of the ball and adds the speed to it, separately for x and y components. The display function sets the color to be used, and draws a circle to the location of the ball

  void move() {
    // Add speed to location
    y = y + speedY;
    x = x + speedX;
  }
  
  void display() {
    fill(c); //set the drawing color
    ellipse(x,y,diameter,diameter); //draw a circle
  }

In addition there is functions that will calculate the x and y values of the ball top, bottom, left, and right sides. These will be used later for collision detection. This kind of small "helper functions" make the main code easier to write, understand, and maintain, as it makes it appear more like a human language instead of being bundle of mathematical functions.

  //functions to help with collision detection
  float left(){
    return x-diameter/2;
  }
  float right(){
    return x+diameter/2;
  }
  float top(){
    return y-diameter/2;
  }
  float bottom(){
    return y+diameter/2;
  }

If you did not understand the internals of the object completely, that is still fine. Important is to acknowledge, that objects can be created, and they contain parameters and functions that can be used to achieve wanted operation. You can copy the whole Ball class to your pong game sketch from below.

class Ball { 
  float x;
  float y;
  float speedX;
  float speedY;
  float diameter;
  color c;
  
  Ball(float tempX, float tempY, float tempDiameter) {
    x = tempX;
    y = tempY;
    diameter = tempDiameter;
    speedX = 0;
    speedY = 0;
    c = (225); 
  }

  void move() {
    // Add speed to location
    y = y + speedY;
    x = x + speedX;
  }
  
  void display() {
    fill(c);
    ellipse(x,y,diameter,diameter);
  }

  //functions to help with collision detection
  float left(){
    return x-diameter/2;
  }
  float right(){
    return x+diameter/2;
  }
  float top(){
    return y-diameter/2;
  }
  float bottom(){
    return y+diameter/2;
  }
}

As we now have the template of a ball as the Ball class, lets make on ball object to our game. First, we define the ball as a global object outside the setup and draw functions in order to access it everywhere in the code. Then, in setup() we create the ball object by calling the Ball class constructor with the new operator. To the constructor we pass the wanted x and y coordinates of the ball, as well as the diameter. The coordinates are set to the center of the window by using width and height parameters available in Processing

Ball ball; // Define the ball as a global object 

void setup(){
  size(800,600);
  ball = new Ball(width/2, height/2, 50); //create a new ball to the center of the window
}

In order to see the ball, we still need to make the program draw it on the screen.

void draw(){
  background(0); //clear canvas
  ball.display(); // Draw the ball to the window
}

Step 4: Setting the Movement of the Ball

The next step is to have the ball to move in the game. In the last step we already implemented movement as a method of the Ball class, so now we just need to use it. This can be done by giving the ball some speed in the setup. We will give the ball a constant speed of 5 in the direction of x-axis, and random speed between -3 and 3 in the direction of y-axis.

void setup(){
  size(800,600);
  ball = new Ball(400,300,50); //create a new ball to the center of the window
  ball.speedX = 5; // Giving the ball speed in x-axis
  ball.speedY = random(-3,3); // Giving the ball speed in y-axis
}

In order for the ball to move, we need to call its move method. This will be done during each cycle of draw(), and we want to do it before actually drawing the ball to the screen.

void draw(){
  background(0); //clear canvas
  ball.move(); //calculate a new location for the ball
  ball.display(); // Draw the ball on the window
}

Now we can see the ball moving to right on the window.

Step 5: Colliding the Ball With the Walls

In previous step we made the ball move. But as we can see when we run the sketch, the ball does not stay inside the window, but instead continues out of it without a problem. In order to keep the ball inside the window, we need to be able to detect when it is colliding with the edges of the window. This can be achieved using some basic comparisons and if statement.

  if (ball.right() > width) { //if stuff between () is true, execute code between {}
    ball.speedX = -ball.speedX;
  }

What this code does is that it compares the ball right x coordinate with the window right edge coordinate, which is same as width of the window. If the ball right x coordinate is larger than the window width, it means that the ball is going over the edge of the window, and the code between the curly brackets will be executed. There we simulate bouncing off the wall by reversing the speed in x direction. Now all what is left to do is to implement same functionality to detect collisions with all the borders in the draw() function.

  if (ball.left() < 0) {
    ball.speedX = -ball.speedX;
  }

  if (ball.bottom() > height) {
    ball.speedY = -ball.speedY;
  }

  if (ball.top() < 0) {
    ball.speedY = -ball.speedY;
  }

Now when running the code the ball should bounce off the walls and stay inside the window.

Step 6: Adding the Paddles

In order to play Pong, we need paddles to hit the ball with. For the paddles, we can create same kind of object as we did for the ball, with the difference that instead of diameter, we now have width and height (w and h), and we draw a rectangle.

class Paddle{

  float x;
  float y;
  float w;
  float h;
  float speedY;
  float speedX;
  color c;
  
  Paddle(float tempX, float tempY, float tempW, float tempH){
    x = tempX;
    y = tempY;
    w = tempW;
    h = tempH;
    speedY = 0;
    speedX = 0;
    c=(255);
  }

  void move(){
    y += speedY;
    x += speedX;
  }

  void display(){
    fill(c);
    rect(x-w/2,y-h/2,w,h);
  } 
  
  //helper functions
  float left(){
    return x-w/2;
  }
  float right(){
    return x+w/2;
  }
  float top(){
    return y-h/2;
  }
  float bottom(){
    return y+h/2;
  }
}

The paddles are created the same way as the ball. First they are defined as global objects.

Paddle paddleLeft;
Paddle paddleRight;

Then they are created in the setup(), and finally moved and displayed in the draw() function. Here we create paddles that are 30 pixels wide and 200 pixels high, and are located in the middle of the window, 15 pixels from the walls.

  paddleLeft = new Paddle(15, height/2, 30,200);
  paddleRight = new Paddle(width-15, height/2, 30,200);

Then we add the movement handling and displaying to the draw(), as we did with the ball.

  paddleLeft.move();
  paddleLeft.display();
  paddleRight.move();
  paddleRight.display();

Step 7: Moving the Paddles, Key Input

To play the game, players needs to be able to move the paddles with the keyboard. This is done using keyPressed() and keyReleased() function in Processing. These are independent functions that get called when a key on the keyboard is pressed or released. In them, we can use if statement to determine which key was pressed. When the key is pressed, we set the paddle speed accordingly, and when it is released, we will stop the paddle by setting the speed to 0.

void keyPressed(){
  if(keyCode == UP){
    paddleRight.speedY=-3;
  }
  if(keyCode == DOWN){
    paddleRight.speedY=3;
  }
  if(key == 'a'){
    paddleLeft.speedY=-3;
  }
  if(key == 'z'){
    paddleLeft.speedY=3;
  }
}

void keyReleased(){
  if(keyCode == UP){
    paddleRight.speedY=0;
  }
  if(keyCode == DOWN){
    paddleRight.speedY=0;
  }
  if(key == 'a'){
    paddleLeft.speedY=0;
  }
  if(key == 'z'){
    paddleLeft.speedY=0;
  }
}

Now you should be able to move the paddles up and down with A and Z keys, as well as up and down arrow keys. You may notice that the paddles may move outside of the window from top and bottom. To prevent this, we can create same kind of collision detection as for the ball, in the draw(). Now instead of reversing the paddle movement direction, we set the y value to be on the maximum position in which the paddle is still inside the window. This way, if the paddle gets out of the window, it will be immediately returned inside.

  if (paddleLeft.bottom() > height) {
    paddleLeft.y = height-paddleLeft.h/2;
  }

  if (paddleLeft.top() < 0) {
    paddleLeft.y = paddleLeft.h/2;
  }
    
  if (paddleRight.bottom() > height) {
    paddleRight.y = height-paddleRight.h/2;
  }

  if (paddleRight.top() < 0) {
    paddleRight.y = paddleRight.h/2;
  }

Step 8: Colliding Ball and Paddle

To finally make the game playable, we need to add the ability to block the ball with the paddle. For this we need to detect the collision between the ball and the paddles. This is done in the draw by first detecting when the ball right or left edge x coordinate is getting behind the paddle edge coordinate, and then determining if the ball is in the paddle area. Here the && in the if statement works as logical AND operator, meaning that all the conditions must be true for the if statement to succeed. In the case of collision the ball just bounces off the paddle, as its speed in x-axis direction is reversed

  // If the ball gets behind the paddle 
  // AND if the ball is int he area of the paddle (between paddle top and bottom)
  // bounce the ball to other direction

  if ( ball.left() < paddleLeft.right() && ball.y > paddleLeft.top() && ball.y < paddleLeft.bottom()){
    ball.speedX = -ball.speedX;
  }

  if ( ball.right() > paddleRight.left() && ball.y > paddleRight.top() && ball.y < paddleRight.bottom()) {
    ball.speedX = -ball.speedX;
  }

Step 9: Scoring and Starting a New Game

To make the game more interesting to play, we can add a way to keep track of the score. To do this, we need to detect when the ball goes past the paddle and to the wall. You may recall that we already have the code for this in the draw().

  if (ball.right() > width) {
    ball.speedX = -ball.speedX;
  }
  if (ball.left() < 0) {
    ball.speedX = -ball.speedX;
  }

Currently the collision with the left and right walls causes the ball to bounce of and change its direction of motion. Now instead we want to add point to the player and return the ball to the middle and start the game again. To keep the score, we will define two global integer variables and set the initial value to 0.

int scoreLeft = 0;
int scoreRight = 0;

When the collision to the wall happens, we want to increase the score of the opponent.

  if (ball.right() > width) {
    scoreLeft = scoreLeft + 1;
    ball.speedX = -ball.speedX;
  }
  if (ball.left() < 0) {
    scoreRight = scoreRight + 1;
    ball.speedX = -ball.speedX;
  }

Instead of changing its direction, we also want to return the ball to the middle of the screen to start a new round.

  if (ball.right() > width) {
    scoreLeft = scoreLeft + 1;
    ball.x = width/2;
    ball.y = height/2;
  }
  if (ball.left() < 0) {
    scoreRight = scoreRight + 1;
    ball.x = width/2;
    ball.y = height/2;
  }

In order to keep the score, we will display the score in the top of the window. This will be implemented in the end of draw(). First we need to set the rules for writing text using textSize() and textAling().

  
  textSize(40);
  textAlign(CENTER);

The score can be displeyed in the top of the screen

  text(scoreRight, width/2+30, 30); // Right side score
  text(scoreLeft, width/2-30, 30); // Left side score

Step 10: Control the Ball Bouncing Direction

In order to make the game more interesting, we can make the ball bounce to different directions from the paddles. This can be done by altering the y direction speed of the ball when it hits the paddle. Instead of making it random (which you can do if you wish), we can use the position of the paddle where the ball hit it to calculate new y speed for the ball.

  // If the ball gets behind the paddle 
  // AND if the ball is int he area of the paddle (between paddle top and bottom)
  // bounce the ball to other direction

  if ( ball.left() < paddleLeft.right() && ball.y > paddleLeft.top() && ball.y < paddleLeft.bottom()){
    ball.speedX = -ball.speedX;
    ball.speedY = ball.y - paddleLeft.y;
  }

  if ( ball.right() > paddleRight.left() && ball.y > paddleRight.top() && ball.y < paddleRight.bottom()) {
    ball.speedX = -ball.speedX;
    ball.speedY = ball.y - paddleRight.y;
  }

What is done here is that we calculate the distance of the point where the ball hit the paddle from the center of the paddle and use that as a new y speed. You can run this and notice, that the ball may get very high y speed in this configuration. Instead we would like to limit the maximum speed the ball can get to, lets say, 10 (and -10). This can be achieved by using the map() function. It essentially allows us to map the value from one scale to another without having to think about the math ourselves.

  if ( ball.left() < paddleLeft.right() && ball.y > paddleLeft.top() && ball.y < paddleLeft.bottom()){
    ball.speedX = -ball.speedX;
    ball.speedY = map(ball.y - paddleLeft.y, -paddleLeft.h/2, paddleLeft.h/2, -10, 10);
  }

  if ( ball.right() > paddleRight.left() && ball.y > paddleRight.top() && ball.y < paddleRight.bottom()) {
    ball.speedX = -ball.speedX;
    ball.speedY = map(ball.y - paddleRight.y, -paddleRight.h/2, paddleRight.h/2, -10, 10);
  }

Step 11: BONUS: Add Arduino Control

You have already created a fully playable pong. However, in the original pong, the paddles were controlled by potentiometers. Here as a bonus exercise, you can connect Arduino with two potentiometers to the Pong Processing sketch, and implement the controls from there.

In order to do this, we need to wire two potentiometers to Arduino board according to the schematic. Open Arduino IDE and open comminucation sketch from Examples > Communication > SerialCallResponse and upload it to your Arduino. In the end of the sketch there is a example code for Processing, from where we can selectively copy parts to use in our Pong game.

First we need to include the serial communications library and define some global variables. like the serial port, array for the serial data received, counter of how many serial bytes we receive and a flag to detect if we have contact with the Arduino.

import processing.serial.*;
Serial myPort;                       // The serial port
int[] serialInArray = new int[3];    // Where we'll put what we receive
int serialCount = 0;                 // A count of how many bytes we receive
boolean firstContact = false;        // Whether we've heard from the microcontroller

In the Setup() we add the code for connecting to the Arduino. The code will print you a list of the serial ports in the Processing console, where you can select the number of port you have your arduino connected to.

    // Print a list of the serial ports for debugging purposes
    // if using Processing 2.1 or later, use Serial.printArray()
    println(Serial.list());

    // I know that the first port in the serial list on my Mac is always my FTDI
    // adaptor, so I open Serial.list()[0].
    // On Windows machines, this generally opens COM1.
    // Open whatever port is the one you're using.
    String portName = Serial.list()[0];
    myPort = new Serial(this, portName, 9600);

To receive values from the Arduino, we add a fuction SerialEvent(). In this function we handle the incoming serial data. The phases of the function are explained in the comments of the code.

void serialEvent(Serial myPort) {
    // read a byte from the serial port:
    int inByte = myPort.read();
    // if this is the first byte received, and it's an A, clear the serial
    // buffer and note that you've had first contact from the microcontroller.
    // Otherwise, add the incoming byte to the array:
    if (firstContact == false) {
      if (inByte == 'A') {
        myPort.clear();          // clear the serial port buffer
        firstContact = true;     // you've had first contact from the microcontroller
        myPort.write('A');       // ask for more
      }
    }
    else {
      // Add the latest byte from the serial port to array:
      serialInArray[serialCount] = inByte;
      serialCount++;

      // If we have 3 bytes:
      if (serialCount > 2 ) {
        //Read the potentiometer values and map them to paddle y locations
        paddleLeft.y = map(serialInArray[0], 0, 255, paddleLeft.h/2, height-paddleLeft.h/2);
        paddleRight.y = map(serialInArray[1], 0, 255, paddleRight.h/2, height-paddleRight.h/2);

        // Send a capital A to request new sensor readings:
        myPort.write('A');
        // Reset serialCount:
        serialCount = 0;
      }
    }
}

If you now have the Arduino connected and right serial port selected, you should be able to control the paddles with the potentionmeters