Arduino Tutorial: Creating Pong

An Arduino tutorial about making a pong game with an LCD and some push buttons. By Felipe Laso-Marsetti.

Leave a rating/review
Save for later
Share
You are currently viewing page 5 of 6 of this article. Click here to view the first page.

Drawing Player 2's paddle

Having looked at how to draw the paddle for player 1, check for input, and correctly detect for collisions against the screen, you are now ready to do the same for player 2.

As a challenge, try to do all of this without looking at the resulting code. Again, it shouldn't be too difficult because almost all of the code for player 1 is reusable for player 2.

If you prefer to look at the solution or get stuck then go ahead and take a peek below.

Start by adding some constants for player 2 at the top of the file:

// Variables to store the pin number for each player's up/down buttons
const byte oneUpButton     = 19; // Pin A5
const byte oneDownButton   = 2;
const byte twoUpButton     = 12; // For Player 2
const byte twoDownButton   = 3;  // For Player 2

// X position of each paddle
const byte onePaddleX      = 10;
const byte twoPaddleX      = 118; // For Player 2

// Y position of each paddle
short onePaddleY           = 28;
short twoPaddleY           = 28;  // For Player 2

The bytes to store the pin number for player 2 correspond to the connections you made earlier for the buttons. The X position of the paddle for player 2 is all the way to the right with the same amount of spacing between the screen and the paddle. Finally, the initial Y position of the paddle for player 2 is the same as that of player 1.

Update draw to include a call to draw the paddle for player 2:

void draw()
{
  GLCD.ClearScreen();
    
  GLCD.DrawVLine(onePaddleX, onePaddleY, paddleHeight);
  GLCD.DrawVLine(twoPaddleX, twoPaddleY, paddleHeight); // For Player 2
}

This is, again, passing the X and Y position of the paddle for player 2, as well as the height of the paddle. You need to setup the pins to check for input for the up and down buttons corresponding to player 2. Update setup as follows:

void setup()
{
  // Player 1 buttons setup
  pinMode(oneUpButton, INPUT);
  digitalWrite(oneUpButton, HIGH); // Use built in pull-up resistor
  pinMode(oneDownButton, INPUT);
  digitalWrite(oneDownButton, HIGH); // Use built in pull-up resistor

  // Player 2 buttons setup
  pinMode(twoUpButton, INPUT);
  digitalWrite(twoUpButton, HIGH); // Use built in pull-up resistor
  pinMode(twoDownButton, INPUT);
  digitalWrite(twoDownButton, HIGH); // Use built in pull-up resistor

  // Initialize the library to draw dark pixels on a light background
  GLCD.Init();
}

Once again you set the mode of each pin to INPUT and then call digitalWrite on each pin with a value of HIGH, to active the pull-up resistor. Now update checkInput as shown:

void checkInput()
{
  // Get the state of each push button corresponding to player 1
  bool oneUpState = !digitalRead(oneUpButton);
  bool oneDownState = !digitalRead(oneDownButton);

  // Get the state of each push button corresponding to player 2
  bool twoUpState = !digitalRead(twoUpButton); 
  bool twoDownState = !digitalRead(twoDownButton);

  // Small delay to cleanup and improve the readings
  delay(1);
  ...
  // Code to check collision for player 1's paddle
  ...

  if (twoDownState)
  {
    twoPaddleY += paddleSpeed;
    
    if (twoPaddleY >= height - paddleHeight)
    {
      twoPaddleY = height - paddleHeight;
    }
  }
  else if (twoUpState)
  {
    twoPaddleY -= paddleSpeed;
    
    if (twoPaddleY <= 0)
    {
      twoPaddleY = 0;
    }
  }
}

You get the input for the up and down button for player two and then check for collisions against the top and bottom of the screen. The logic is the same as player 1 except tweaked to update the y position of player 2's paddle.

Upload the sketch to your Arduino and push the up and down buttons for player 2.
Right Paddle

Congrats :] you're almost done, all that remains is the ball and its collisions.

Coding the ball and collision detection

Pong Is Coming

To kick off the final portion of the tutorial, and to make the complete pong game with the ball and collisions, add the following constants and variables at the top of the header file:

// Size of the ball and paddle
const byte paddleHeight    = 8;
const byte ballSize        = 2;  // Size of the ball
const byte ballPadding     = 1;  // Padding because GLCD adds an extra pixel to the size you set the ball to

// X and Y position of the ball
short ballX                = 63;
short ballY                = 31;

// Speed of the ball for the X and Y directions, and speed of the paddle movement vertically
short ballSpeedX           = 3;  // Speed of the ball in the horizontal direction
short ballSpeedY           = 2;  // Speed of the ball in the vertical direction
short paddleSpeed          = 2;

The variables you create correspond to the ball size and some padding required when working with GLCD, the X and Y position of the ball, and the X and Y speed of the ball. You want the ball to move at different speeds because the screen is not square, it's wider so horizontally the ball should move quicker to make the game challenging.

From there, update draw to include a call to draw the ball:

void draw()
{
  GLCD.ClearScreen();
    
  GLCD.DrawVLine(onePaddleX, onePaddleY, paddleHeight);
  GLCD.DrawVLine(twoPaddleX, twoPaddleY, paddleHeight);
 
  // Draw the ball by passing in the X and Y position, and the width and height   
  GLCD.FillRect(ballX, ballY, ballSize, ballSize);
}

The last line is what you updated in the method. It draws a filled rectangle with the set X and Y position, and with the width and height (which in this case is the same so it's a perfectly square ball).

Add a call to two new methods inside loop:

void loop()
{  
  unsigned long now = millis();

  if ((now - lastRefreshTime) > refreshInterval)
  {    
    checkInput();
    checkCollisions(); // New method to check for the ball's collisions
    update();          // New method to update the position of the ball on each frame
    draw();
    
    lastRefreshTime = now;
  }
}

You add a method called checkCollisions that, as the name implies, checks for collisions between the ball and screen, and the ball and paddles. The other method is called update and is going to be in charge of updating the X and Y position of the ball in each frame. Write the code for update as follows:

/*
 * Function to update the X and Y position of the ball;
 */
void update()
{
  ballX += ballSpeedX;
  ballY += ballSpeedY;
}

On every call to update you add the X and Y speed of the ball. The speed can be positive or negative, to indicate if the vertical movement is up or down, and the horizontal movement is left or right. Move on to checkCollisions, the beefiest method of them all:

/*
 * Checks collisions between the ball and walls, and the ball and paddles
 */
void checkCollisions()
{
  // 1
  // Check vertical collisions
  if (ballSpeedY > 0)
  {
    // 2
    // Limit collision checks to the ball and bottom
    if (ballY + ballSize + ballPadding >= height)
    {
      ballY = height - ballSize - ballPadding;
      
      ballSpeedY *= -1;
    }
  }
  else
  {
    // 3
    // Limit collision checks to the ball and top
    if (ballY <= 0)
    {
      ballY = 0;
      
      ballSpeedY *= -1;
    }
  }
  
  // 4
  // Check horizontal collisions
  if (ballSpeedX > 0)
  {
    // 5
    // Limit collision checks to the ball and right edge of the screen
    if (ballX + ballSize + ballPadding > width)
    {
      reset();
    }
    else if (ballX >= twoPaddleX-ballSpeedX && (ballY - ballSize >= twoPaddleY && ballY <= twoPaddleY + paddleHeight))
    {
      ballSpeedX *= -1;
      
      ballX = twoPaddleX - ballSize - ballPadding;
      
      draw();
    }
  }
  else
  {
    // 6
    // Limit collision checks to the ball and left edge of the screen
    if (ballX < 0)
    {
      reset();
    }
    else if (ballX <= onePaddleX+ballSpeedY && (ballY - ballSize >= onePaddleY && ballY <= onePaddleY + paddleHeight))
    {
      ballSpeedX *= -1;
      
      ballX = onePaddleX + ballPadding;
      
      draw();
    }
  }
}

Let's go over each numbered section:

  1. The first if statement checks for vertical collisions, meaning if the ball has collided with the top or bottom of the screen.
  2. If the ball's vertical speed is positive then it's moving down, and thus you check for collisions against the bottom portion of the screen. If a collision occurred you reset the ball's position to right up against the screen and change the vertical speed of the ball to be negative, so it now moves up.
  3. This is the same process as before except you now check for collisions against the top of the screen. Should the ball collide with it then you reset its position and change the direction of the Y speed.
  4. Now come all the checks for horizontal collisions. Either between the ball and paddles or the ball and the screen. If the X speed is positive then the ball is moving towards the right side of the screen. This check is useful as it spares you from checking for collisions in all four scenarios. Instead you can focus your checks against the right side of the screen and the right paddle, optimizing performance.
  5. In this conditional check you verify the ball's position against the right side of the screen. If the ball is outside of the screen then reset is called. You'll write that method in just a second, but all it does is reset the game so you can play again without having to hit reset on your Arduino. If the ball didn't collide against the edge of the screen then you check for collisions against the paddle. If this is the case then you change its X speed, X position, and call draw to update the screen.
  6. This is the same logic as before, except now you are first checking for collisions against the left side of the screen and then left paddle.

Last but not least, write the code for reset as follows:

/*
 * Resets the paddles and ball to their initial state should either player loose
 */
void reset()
{
  // Reset the game to the initial state;
  ballX = 63;
  ballY = 31;
  onePaddleY = 28;
  twoPaddleY = 28;
  ballSpeedX = 3;
  ballSpeedY = 2;
  
  draw();
  
  delay(3000);
}

This reset's the ball's X and Y position, the X and Y speed, and also the paddle's Y position. Afterwards you call draw to re-draw everything on screen and delay execution of your program for 3 seconds (3000 milliseconds) in order for players to get ready for the next round.

Upload your sketch one last time and give your pong game a test.
Final Pong

GREAT SUCCESS!!! :]

You've done it. You have gone from having a bunch of electric components, an Arduino board, and an LCD screen, to a fully functional pong game. This is your own little console, you've made something akin to the first Game Boy! A huge congratulations to you! Hopefully this is the beginning of many more fun circuits and video games with Arduino for you.
I Did It

You can download the final project here. It includes the Arduino sketch along with the GLCD library, just in case.