Coding a 2D Pong Game in Java
Pong is an old computer game based on table tennis. The game has a ball and players with paddles. The players move their paddle to block the ball from getting past their side of the screen. If a player misses the ball, they lose the round and their opponent gains a point.
In this guide, you will learn how to program a basic version of Pong in Java. In this version, the game will be played against the PC, rather than another player. The game will be developed using object-oriented practices, so it may be extended or modified with relative ease down the line.
Difficulty: This is designed to be beginner friendly, but you should have some basic experience with Java and object-oriented programming. If you’re in school, this is a program you should be able to complete by the end of your first computer science/programming class.
Getting Started
Rules
- The ball starts off near the center of the window, and moves in a set direction.
- The player controls a paddle on the left with their mouse. The PC controls a paddle on the right by following the ball’s position.
- The ball may bounce off either paddle, or the top and bottom of the screen.
- As the ball bounces back and forth, its speed will slowly increase, raising the game’s difficulty.
- If the ball gets past either paddle, the opposing player gets a point and the game resets.
The game will contain 4 classes.
- Main – Launches the program, starts the game and game timer
- PongGame – Extends JPanel, serves as the game’s canvas, tracks mouse motion, handles game logic
- Ball – An object representing the ball. Has variables for color, size, direction, and position. Contains methods to move and draw the ball.
- Paddle – An object representing a paddle. Has a color, size, position, speed. Contains a method to move up and down.
An important goal is to design this program in a way that’s extendable. If you wanted, you could program this entire application in one or two classes. We break the code up into multiple classes with their own unique purpose in order to keep the code maintainable and extendable. Rather than hard-coding everything, the code should be reusable. For example, by putting objects like Balls and Paddles in their own classes, we can easily add additional paddles and balls to the game down the line if we want. You could even reuse the paddle or ball in another similar 2D game down the road if you wanted to!
Begin by creating a new Java project in your favorite IDE. If you follow along with the video, I use IntelliJ IDEA. I selected OpenJDK 18 for the project’s JDK, though there shouldn’t be any issues using older versions. You may start with the sample code/hello world program. Test the program to make sure your build config is set properly and the program runs or prints out “hello world” properly.
The Game Window
We’re going to be using components from swing and awt for this project. Let’s start by creating a JFrame to contain the game in the Main class.
import javax.swing.*;
public class Main {
//declare and initialize the frame
static JFrame f = new JFrame("Pong");
public static void main(String[] args) {
//make it so program exits on close button click
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//the size of the game will be 480x640, the size of the JFrame needs to be slightly larger
f.setSize(650,495);
//show the window
f.setVisible(true);
}
}
Code language: JavaScript (javascript)
The comments explain what each line does. Be sure to import swing.
At this point, test the code. A blank rectangular window should open. Closing the window should end the program.
PongGame Class
Create a new class file called PongGame in your project. This class will contain all the important game logic and other objects required. The PongGame class will extend the JPanel class from swing, so swing must be imported. It will function as the canvas for the game where all the objects are painted on the screen.
import javax.swing.*;
public class PongGame extends JPanel {
}
Code language: JavaScript (javascript)
Additionally, create static final ints to store the width and height of the drawing area. I called mine WINDOW_HEIGHT and WINDOW_WIDTH with a resolution of 640×480.
This should go at the top of the class, just after the class declaration.
static final int WINDOW_WIDTH = 640, WINDOW_HEIGHT = 480;
Code language: PHP (php)
Now add the public void paintComponent(Graphics g)
method to the PongGame class. This method is where we can use the Graphics class to draw our objects. Remember, the coordinate system starts at x = 0 and y = 0 in the UPPER LEFT, not the lower left. It’s slightly different from the Cartesian plane you’d usually use in most Math classes.
In the new paintComponent method, we set the brush color to black and paint a rectangle starting at 0,0. It will extend out a width of 640, the WINDOW_WIDTH constant, and a height of 480, the WINDOW_HEIGHT constant.
java.awt must also be imported in order to use the Color and Graphics classes.
Now, the class file now looks like this:
import javax.swing.*;
import java.awt.*;
public class PongGame extends JPanel {
static final int WINDOW_WIDTH = 640, WINDOW_HEIGHT = 480;
/**
* Updates and draws all the graphics on the screen
*/
public void paintComponent(Graphics g){
//draw the background, set color to BLACK and fill in a rectangle
g.setColor(Color.BLACK);
g.fillRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
}
}
Code language: JavaScript (javascript)
At this point, our JPanel/PongGame is almost ready to test. However, we must first add it to the JFrame created in the main method. Return to your Main class, initialize a new “PongGame” object, and then use the f.add(comp)
method to add the new PongGame to the frame.
Your Main class should look like this after creating the PongGame and adding it to the frame.
Main.java
import javax.swing.*;
public class Main {
//declare and initialize the frame
static JFrame f = new JFrame("Pong");
public static void main(String[] args) {
//make it so program exits on close button click
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//the size of the game will be 480x640, the size of the JFrame needs to be slightly larger
f.setSize(650,495);
//make the new PongGame
PongGame game = new PongGame();
//add the game to the JFrame
f.add(game);
//show the window
f.setVisible(true);
}
}
Code language: JavaScript (javascript)
When you run it, it should display a black window. If you resize the screen, you’ll see the black area only covers the 640×480 area defined earlier.
Making The Ball
Now that we have a blank canvas, our goal is to create a ball object. It will bounce around the screen to start, and eventually also bounce off paddles.
Begin by creating a new Ball class. It will have the following instance variables:
- ints: x, y, cx, cy, size, speed
- Color: color
Note on Variable Scope: It is normal practice to give instance variables the scope of “private” in Java. This prevents outside classes from modifying the variables directly without the use of setters and getters. It protects the instance by ensuring only the corresponding class/object can modify its own information. This helps with code maintainability.
The variables x and y represent the location of the ball’s upper left corner. The cx and cy variables are the change in x or y per frame. For example, if cx is set to 3, that means the x position of the ball is increasing by 3 per frame, so the ball will move three spaces to the right every time the screen is updated. If cx was negative, it would move to the left. If cy is positive, the ball moves down, if cy is negative, it moves up.
The size variable will hold the balls size. This will be the width and height of the ball when we fill the oval to paint it.
The speed is related to the cx and cy variables, except it is always positive.
The color simply tells us what color the ball will be painted with.
Attempt to declare the instance variables and make the constructor above. The constructor should take all the instance variables as parameters and set them accordingly. Color can be imported with import java.awt.*;
It should look something like this when complete.
public class Ball {
//declare instance variables
private int x, y, cx, cy, speed, size;
private Color color;
//ball constructor assigns values to instance variables
public Ball(int x, int y, int cx, int cy, int speed, Color color, int size) {
this.x = x;
this.y = y;
this.cx = cx;
this.cy = cy;
this.speed = speed;
this.color = color;
this.size = size;
}
}
Code language: JavaScript (javascript)
Now that we have variables for the ball’s position, size, and color ready to use, we can work on the method to draw the ball. Create a new method called “paint” in the Ball class which takes Graphics g as an argument.
Then, draw the ball by setting the brush color to the ball’s color, and filling an oval at the appropriate location.
public void paint(Graphics g){
//set the brush color to the ball color
g.setColor(color);
//paint the ball at x, y with a width and height of the ball size
g.fillOval(x, y, size, size);
}
Code language: JavaScript (javascript)
Now it’s time to add a Ball to the game and paint it. In the PongGame class, declare a new Ball object and initialize it with an appropriate location (towards the center), size, and color. I went with a cx, cy, and speed of 3 for this. I created a constructor for PongGame in the PongGame class and put this code there:
PongGame.java
private Ball gameBall;
public PongGame(){
gameBall = new Ball(300, 200, 3, 3, 3, Color.YELLOW, 10);
}
Code language: PHP (php)
Remember, a PongGame was already created in the main method. So when the constructor is called, the gameBall will be initialized with the information we just setup. We just need to paint it in the PongGame’s paintComponent method by calling the paint method we just created for the Ball class. Remember to pass the Graphics object g to the Ball’s paint method.
PongGame.java
public void paintComponent(Graphics g){
//draw the background
g.setColor(Color.BLACK);
g.fillRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
//draw the ball
gameBall.paint(g);
}
Code language: JavaScript (javascript)
Test the program again. You should see the black background created earlier, along with the Ball. Nothing will be moving yet, as we haven’t coded that part.
Game Timer & Ball Animation
It’s time to create the game’s timer. This will be a timer that repeats once every 33 milliseconds, or about 30 times per second. Each execution of the timer’s code will represent a frame in the game. The pong game will be redrawn each frame, and game logic will be handled to detect and change the position of the ball and paddles.
I have decided to place the game timer in the main method of the program. There are multiple types of timer objects available in Java. For manipulating GUI components and 2D games, it’s recommended to use the timer from java.swing, rather than the other timers. This is the format for creating a swing timer:
//remember to import java.swing.* at top of class file. You'll also have to import ActionListener and ActionEvent from java.awt.event
Timer timer = new Timer(DELAY, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//whatever the timer does every repeat goes here
}
});
//start the timer
timer.start();
Code language: PHP (php)
The DELAY argument passed to the Timer’s constructor is the time, in milliseconds, that the timer repeats. The actionPerformed method is where you place the code that is to be repeated. After creating the timer, it must be started by calling the Timer’s start method.
Create a new swing Timer in the main method. Set the delay to 33 and have it repaint the PongGame object every time it runs using the JPanel’s .repaint()
method. Each time repaint is called on a JPanel, the paintComponent method we worked on earlier is executed.
Now, your Main class should look like this:
Main.java
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Main {
//declare and initialize the frame
static JFrame f = new JFrame("Pong");
public static void main(String[] args) {
//make it so program exits on close button click
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//the size of the game will be 480x640, the size of the JFrame needs to be slightly larger
f.setSize(650,495);
PongGame game = new PongGame();
f.add(game);
//show the window
f.setVisible(true);
//make a new Timer
Timer timer = new Timer(33, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//repaint the screen
game.repaint();
}
});
//start the timer after it's been created
timer.start();
}
}
Code language: JavaScript (javascript)
Ball Movement
Next, we need to add some ball animation to the game. In order to achieve this, we need to change the ball’s position slightly on each frame. Open the Ball class and create a new public void method called moveBall. When called, this method will change the ball’s x and y position by one frame by adding cx to x and cy to y.
Ball.java
public void moveBall(){
x += cx;
y += cy;
}
Code language: JavaScript (javascript)
Since cy and cx are both set to 3 when the ball is created in the PongGame class, the ball will move 3 pixels to the right and 3 pixels down each frame.
Next, go to the PongGame class and create a method called gameLogic. This method will oversee the bulk of the operations involved in the game. It will be called every frame by the timer to update the ball position, paddle positions, check who won, etc.
Have your new gameLogic method move the ball one frame.
PongGame.java
/**
* Called once each frame to handle essential game operations
*/
public void gameLogic(){
//move the ball one frame
gameBall.moveBall();
}
Code language: PHP (php)
Finally, return to the timer in the main method of the Main class. Have the timer call the new gameLogic method you just created.
Main.java
//make a new Timer
Timer timer = new Timer(33, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//game logic
game.gameLogic();
//repaint the screen
game.repaint();
}
});
Code language: JavaScript (javascript)
In the code above, I called gameLogic before repainting the screen. So the balls position gets updated and then the screen is repainted. If you reversed this and called repaint before gameLogic, each frame drawn would be 33ms behind the calculations (repainting the previous frame). The difference is negligible in a game like this, but it’s something to keep in mind.
Now the timer will call the game’s gameLogic method, which calls the gameBall’s moveBall method.
Run the program and see what happens. If it works properly, the ball should move down and to the right across the screen before disappearing off the edge. It will just keep going, since we haven’t added bounce logic in yet.
Bouncing Off Edges
Next we will make the ball bounce off the top, bottom, left, and right side of the screen. When the game is finished, it will NOT bounce off the left and right, only the paddles and the top and bottom. For testing, we will have it bounce off all 4 edges.
I have put the bounce logic in the Ball class by adding a bounceOffEdges method. This logic could be done in the PongGame class as well, but I put it in the Ball class because it’s really an action or behavior of the Ball, not the PongGame.
The bounceOffEdges method will take two ints as arguments, the top and bottom y values of the edges it’s “bouncing” off of. I’m going to hard-code the x values in for the left and right sides for demonstration, we’ll take that part out later.
The method declaration in the Ball class should look like this:
public void bounceOffEdges(int top, int bottom){
Code language: JavaScript (javascript)
For the logic, we need to check if the ball’s x or y location is approaching an edge of the screen. When it gets there, we need to reverse cx or cy by multiplying it by -1. Create two additional methods in the Ball class called reverseX and reverseY to handle this behavior.
When complete, this is what the three methods added to the Ball class look like.
Ball.java
/**
* Detect collision with screen borders and reverse direction
* @param top - the y value of the top of the screen
* @param bottom - the y value of the bottom of the screen
*/
public void bounceOffEdges(int top, int bottom){
//if the y value is at the bottom of the screen
if (y > bottom){
reverseY();
}
//if y value is at top of screen
else if(y < top){
reverseY();
}
//if x value is at left or right side
//hard-coded values, we will delete this section later
if(x < 0){
reverseX();
}
else if(x > 640){
reverseX();
}
}
/**
* Reverse's the ball's change in x value
*/
public void reverseX(){
cx *= -1;
}
/**
* Reverse's the ball's change in y value
*/
public void reverseY(){
cy *= -1;
}
Code language: PHP (php)
Finally, call bounceOffEdges from the gameLogic method of the PongGame class.
PongGame.java
public void gameLogic(){
//move the ball one frame
gameBall.moveBall();
//edge check/bounce
gameBall.bounceOffEdges(0, WINDOW_HEIGHT);
}
Code language: JavaScript (javascript)
Run the program and make sure the bounces work. The ball should bounce off all 4 edges of the screen. Note that the demo animation shown is sped up so yours may be slower.
You may have noticed that the ball hits the corners of the top and left side of the screen, but appears to go slightly beyond the right and bottom side of the screen. This is because the ball itself is drawn starting in its upper left corner. So if the ball is drawn at the screen’s bottom or right edge, it will be drawn off screen. To fix this, we can simply subtract the size (width/height) of the ball from the bottom and right when we check if it’s reached those edges of the screen, so the ball reverses before it gets quite to the edge. This is optional, but I think it looks better.
public void bounceOffEdges(int top, int bottom){
//if the y value is at the bottom of the screen
if (y > bottom - size){
reverseY();
}
//if y value is at top of screen
else if(y < top){
reverseY();
}
//if x value is at left or right side
//hard-coded values, we will delete this section later
if(x < 0){
reverseX();
}
else if(x > 640 - size){
reverseX();
}
}
Code language: JavaScript (javascript)
Paddles
Now that we have the Ball mostly working, it’s time to begin on the paddles. The Paddle class will have the following instance variables:
- The ints: height, x, y, speed
- Color: color
Like a Ball, each Paddle will have a position saved in x, y coordinates. It will also have a height, which is the total height of the Paddle in pixels. By doing it this way, you will be able to change the height later, if needed. The Paddle has a speed. The speed is the distance the Paddle may move up or down per frame. Finally, the Paddle will be given a color.
We will also use a constant PADDLE_WIDTH int to store the paddle’s width. I selected a width of 15 for the paddles. I made this a constant rather than an instance variable, as it’s not something I expect to change. However, you may make it an instance variable if you’d like to change it down the line as part of a game mechanic.
Create your new Paddle class, declare the instance variables, set up PADDLE_WIDTH, and create a constructor to set up the values.
Paddle.java
import java.awt.*; //needed for Color
public class Paddle {
//declare instance variables
private int height, x, y, speed;
private Color color;
//constant
static final int PADDLE_WIDTH = 15;
/**
* A paddle is a rectangle/block that can move up and down
* @param x the x position to start drawing the paddle
* @param y the y position to start drawing the paddle
* @param height the paddle height
* @param speed the amount the paddle may move per frame
* @param color the paddle color
*/
public Paddle(int x, int y, int height, int speed, Color color) {
this.x = x;
this.y = y;
this.height = height;
this.speed = speed;
this.color = color;
}
}
Code language: PHP (php)
Just like the Ball, we need a way to draw the paddle. To do this, create a paint method with Graphics g as the parameter.
You may paint the paddle using either g.fillRect(x, y, width, height)
or g.drawRect(x, y, width, height)
. If you use fill, it will be a solid rectangle. If you use draw, it will only draw the borders of the rectangle (it will be black, or your background color in the center). Select the option you think looks best.
I went with fill, but I think draw looks cool too! When complete, your Paddle’s paint method should look like this:
/**
* Paints a rectangle on the screen
* @param g graphics object passed from calling method
*/
public void paint(Graphics g){
//paint the rectangle for the paddle
g.setColor(color);
g.fillRect(x, y, PADDLE_WIDTH, height);
}
Code language: PHP (php)
Now we have a working Paddle class that does something. Return to the PongGame class and create two new Paddle objects. Declare them as instance variables and then initialize them in the PongGame constructor. I named the first Paddle userPaddle, and the second paddle pcPaddle. The userPaddle will be on the left, and the pcPaddle on the right. I started them about halfway down the screen with y values of 200. I made my paddles red and blue with a height of 75 and a speed of 3, though you may select whichever colors and size you prefer.
Here’s what my changes look like:
PongGame.java
static final int WINDOW_WIDTH = 640, WINDOW_HEIGHT = 480;
private Ball gameBall;
private Paddle userPaddle, pcPaddle;
public PongGame() {
gameBall = new Ball(300, 200, 3, 3, 3, Color.YELLOW, 10);
userPaddle = new Paddle(10, 200, 75, 3, Color.BLUE);
pcPaddle = new Paddle(610, 200, 75, 3, Color.RED);
}
Code language: PHP (php)
With our paddles created, it’s almost time to test them out. We still need to draw them from the paintComponent method. Update the PongGame’s paintComponent method to call the paint methods on the new paddles.
PongGame.java
public void paintComponent(Graphics g) {
//draw the background
g.setColor(Color.BLACK);
g.fillRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
//draw the ball
gameBall.paint(g);
//draw the paddles
userPaddle.paint(g);
pcPaddle.paint(g);
}
Code language: JavaScript (javascript)
Time to test again! Run the program and make sure the paddles look good. You may modify the code or Paddle constructors as needed if you want to style or position them differently. I wouldn’t make the paddle width constants any lower than 10, as we will need them to be a certain width down the road for ball/paddle collision detection. The paddles won’t move yet.
Paddle Movement
The paddles will move several pixels per frame based on their speed towards a target position. Create a new method called moveTowards with an int parameter. The paddle will move up or down towards this value and stop if it’s centered on the location.
Paddle.java
/**
* Move the paddle towards this y position every frame (centered)
* @param moveToY - position the paddle is centered on
*/
public void moveTowards(int moveToY) {
}
Code language: PHP (php)
Before we can determine if we need to move the paddle up or down, we must first figure out what the y position is of the center of the paddle. Remember, the y position of the paddle is the spot it draws the top of the paddle. The y position of the paddle’s center is going to be equal to the y position of the paddle, plus the paddle’s height divided by two.
Paddle.java
public void moveTowards(int moveToY) {
//find the location of the center of the paddle
int centerY = y + height / 2;
}
Code language: JavaScript (javascript)
Now the paddle needs to move up or down towards the given position. Check if the centerY is below moveToY, if it is, move the paddle up. If the opposite is true, move the paddle down. If the paddle’s where it’s supposed to be, keep it where it is.
The code for moving up and down looks like this:
public void moveTowards(int moveToY) {
//find the location of the center of the paddle
int centerY = y + height / 2;
//if the center of the paddle is too far down
if(centerY > moveToY){
//move the paddle up by the speed
y -= speed;
}
//if the center of the paddle is too far up
if(centerY < moveToY){
//move the paddle down by speed
y += speed;
}
}
Code language: JavaScript (javascript)
Recall that I set both Paddle’s speed to 3 earlier. Consider this situation: The paddle is centered at position 100, we called moveTowards(102), the paddle moves down by 3 (the paddle’s speed). The paddle is now centered at y position 103. moveTowards(102) is called again in the next frame. The paddle moves up by 3, back to position 100. The cycle repeats itself. The paddle is now stuck jiggling up and down around the position it’s supposed to move towards.
To stop this from happening, we need to add an additional check before we move the paddle. If the paddle is within a 3 (or the speed) space margin of where it’s supposed to be, we won’t move it at all. We’ll say it’s OK to leave the paddle center off slightly.
To calculate this, we need to create a condition to check how far the paddle’s center y is off from the moveToY by getting the difference of their values. Speed is always positive, so the difference also needs to always be positive. We can take the absolute value of the difference to calculate the difference as a positive value each time. Once we find the difference, if it’s greater than the speed, we’re good to move the paddle.
Here is what my updated moveTowards method looks like:
/**
* Move the paddle towards this y position every frame (centered)
* @param moveToY - position the paddle is centered on
*/
public void moveTowards(int moveToY) {
//find the location of the center of the paddle
int centerY = y + height / 2;
//determine if we need to move more than the speed away from where we are now
if(Math.abs(centerY - moveToY) > speed){
//if the center of the paddle is too far down
if(centerY > moveToY){
//move the paddle up by the speed
y -= speed;
}
//if the center of the paddle is too far up
if(centerY < moveToY){
//move the paddle down by speed
y += speed;
}
}
}
Code language: JavaScript (javascript)
Finally, let’s test the newly created method. To do this, return to the gameLogic() method of the PongGame class. Each time this method is executed by the timer, have the paddles move towards a preselected y position. I tested mine with the userPaddle moving towards position 0, and the pcPaddle moving towards position 600. The paddles should move up or down towards the specified location by 3 spaces a frame.
//in the gameLogic method
userPaddle.moveTowards(0);
pcPaddle.moveTowards(600);
Code language: JavaScript (javascript)
Paddle Control
Now that we have a ball and some paddles, it’s time to make it so the user and the PC can move their paddles.
Tracking The Mouse
Let’s begin by making the user’s paddle follow the user’s mouse position. To do this, we need to have the PongGame class implement a MouseMotionListener. If you’re not familiar with MouseMotionListener, its name describes its function. It detects or “listens” for mouse events and allows us to do things with them.
To implement the MouseMotionListener, just add the keyword implements followed by the interface name MouseMotionListener to the class declaration for PongGame.
In full, your PongGame class declaration should now look like this:
public class PongGame extends JPanel implements MouseMotionListener {
Code language: PHP (php)
In order to use MouseMotionListner, it must be imported (import java.awt.event.MouseMotionListener). We also need to import MouseEvent (import java.awt.event.MouseEvent) to handle mouse movement.
The final step in implementing MouseMotionListener is to override the mouseDragged and mouseMoved methods. If you’re using IntelliJ, there should be a quick-fix option to automatically add these methods to your class. If not, you may add them manually.
At this point, your PongGame class should look like this:
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
public class PongGame extends JPanel implements MouseMotionListener {
//all the code we wrote already should be here, followed by...
@Override
public void mouseDragged(MouseEvent e) {
}
@Override
public void mouseMoved(MouseEvent e) {
}
}
Code language: JavaScript (javascript)
Note that we won’t be using mouseDragged for this project, but it must be included anyways to implement mouseMotionListener.
Our goal now is to track the y position of the user’s mouse, and then have their paddle move towards that y position by calling the moveTowards method created in the earlier section.
Create a new instance variable in the PongGame class called userMouseY which will store the y position of the user’s mouse as an int. Initialize it with a value of zero in the PongGame constructor.
public class PongGame extends JPanel implements MouseMotionListener {
static final int WINDOW_WIDTH = 640, WINDOW_HEIGHT = 480;
private Ball gameBall;
private Paddle userPaddle, pcPaddle;
private int userMouseY;
public PongGame() {
gameBall = new Ball(300, 200, 3, 3, 3, Color.YELLOW, 10);
userPaddle = new Paddle(10, 200, 75, 3, Color.BLUE);
pcPaddle = new Paddle(610, 200, 75, 3, Color.RED);
//set to zero for now
userMouseY = 0;
//listen for motion events on this object
addMouseMotionListener(this);
}
Code language: PHP (php)
Note the line addMouseMotionListener(this);
above in the constructor. This triggers mouse motion detection. Without this line, the program will not register mouse motion events.
Now, in the mouseMoved method, we need to set userMouseY to the mouse’s y position. We can find the y position of the mouse with the MouseEvent’s getY method.
@Override
public void mouseMoved(MouseEvent e) {
userMouseY = e.getY();
}
Code language: JavaScript (javascript)
Whenever the user moves their mouse, userMouseY will be set accordingly.
Finally, we need to tell the user’s paddle to move towards that y location. Do this in the gameLogic method. Remove any existing moveTowards() method calls you may have created for testing.
public void gameLogic() {
//move the ball one frame
gameBall.moveBall();
//edge check/bounce
gameBall.bounceOffEdges(0, WINDOW_HEIGHT);
//move the paddle towards where the mouse is
userPaddle.moveTowards(userMouseY);
}
Code language: JavaScript (javascript)
Time to test the code. Run the program and move the mouse around. The paddle on the left should start moving towards the y position of your mouse.
PC Player Movement
The next thing to do is make it so the PC can move its paddle. There are many ways we could make the PC opponent work. Here are a few options:
- The PC just moves its paddle at random.
- The PC always tries to move towards the y position of the ball.
- The PC calculates where the ball will hit a few seconds before it gets there, and moves the paddle in that direction. The PC will always win this way, so there’s a 5-10% chance it intentionally misses.
For this exercise, we will use the second option, as it’s the easiest with the current setup. We can just have the PC move the paddle towards the balls y position every frame by calling the moveTowards method. As the ball speed increases, the PC will eventually miss.
The third option may be more realistic and fun. I may write a second guide on implementing this and other features in the future.
Create a getter for the ball to return its y value. Then, in the gameLogic method of the PongGame class, make it so the PC paddle always moves towards the ball’s y position.
Ball.java
//getter in Ball class
public int getY(){
return y;
}
Code language: PHP (php)
PongGame.java
public void gameLogic() {
//move the ball one frame
gameBall.moveBall();
//edge check/bounce
gameBall.bounceOffEdges(0, WINDOW_HEIGHT);
//move the paddle towards where the mouse is
userPaddle.moveTowards(userMouseY);
//move the PC paddle towards the ball y position
pcPaddle.moveTowards(gameBall.getY());
}
Code language: JavaScript (javascript)
Guess what time it is? Time to test the code again. The PC’s paddle should now follow the ball. Since the PC can move its paddle 3 per frame, and the ball’s cy is also 3 or -3, the paddle will always perfectly align with the ball. As the ball speed increases in the future, the paddle won’t always catch up in time and the PC loses.
Paddle/Ball Collision Detection
It’s time to add collision detection between the ball and the paddles, so they can bounce off the paddles.
There are a number of ways we could do this. For this project, I have created a checkCollision(Ball b)
method in the Paddle class. This method will return a boolean (true/false) if the ball is colliding with this paddle. Then we can check this value in gameLogic and call the ball’s reverseX method to change the direction.
I decided to do it this way because a paddle is more specific to the Pong game than a ball is. In the future if I have a game that involves balls and no paddles, I may be able to reuse much of the Ball class.
Having a Ball.checkCollision(Paddle)
method would achieve the same thing, but then the Ball class wouldn’t be as reusable in other games.
Additionally, a checkCollision(Paddle p, Ball b)
method in the PongGame class may be appropriate, if I was trying to make both the Paddle and Ball class reusable in other programs. Onward!
Collision Logic
We want to check if the Ball is colliding with the Paddle. We will achieve this by checking the position of the Ball against the position of the Paddle. For the Ball to be colliding with the Paddle, it must have an x value between the left and right side of the Paddle, and a y value between the top and bottom of the Paddle.
We can get the position of the Ball by calling getters for x and y. A getY() method was created earlier, so do the same and create a getX method in the Ball class.Ball.java
public int getX(){
return x;
}
Code language: PHP (php)
We know the position of the upper left corner of the Paddle based on the Paddle’s x and y variables. So we already have the left and top side accounted for. We need to find the x value of the Paddle’s right side and the y value of the bottom.
Go to the Paddle class and create the checkCollision(Ball b)
method.Paddle.java
/**
*
* @param b the ball we're checking for a collision with
* @return true if collision is detected
*/
public boolean checkCollision(Ball b){
}
Code language: PHP (php)
Create some ints to calculate the bottom and right side positions. The bottom is the y value of the Paddle plus its height. The right is the x value of the Paddle’s x position plus the width constant.
Paddle.java
public boolean checkCollision(Ball b){
int rightX = x + PADDLE_WIDTH;
int bottomY = y + height;
}
Code language: PHP (php)
Now we have values for every side of the paddle. We can check these values against the Ball’s position with conditions. You can do this using a combination of if statements, or you can check all the conditions in one go using the logical and (&&) operator.
I split mine with nested if statements. The outer checks for the x values, the inner the y values, and then it returns true if all conditions pass.
Paddle.java
public boolean checkCollision(Ball b){
int rightX = x + PADDLE_WIDTH;
int bottomY = y + height;
//check if the Ball is between the x values
if(b.getX() > x && b.getX() < rightX){
//check if Ball is between the y values
if(b.getY() > y && b.getY() < bottomY){
//if we get here, we know the ball and the paddle have collided
return true;
}
}
//if we get here, one of the checks failed, and the ball has not collided
return false;
}
Code language: PHP (php)
With the checkCollision method complete, we may now add it to the gameLogic method and make the ball reverse direction when a collision is detected.
Test time. Run the program and move your paddle to see if it collides with the ball. Note that the collision with the pcPaddle may look slightly off, as it’s checking for a collision between the right corner of the ball and the right side of the paddle, so the ball is being drawn inside the paddle even though they are colliding.
Fixing Collision From Left
When the Ball collides with the pcPaddle, it is still being drawn from the upper right, so it appears as if it passes into the paddle and then bounces off, rather than just bouncing off the edge. This step is optional, but if you want it to look like the ball bounces off the left side of the pcPaddle exactly at the edge, you can modify the collision detection of the paddle’s left side.
You may modify the checkCollision method to check if the ball is colliding with a point equal to the ball’s width away from the left side of the paddle. So start the check at the x value of the paddle minus the width of the ball.
Paddle.java
public boolean checkCollision(Ball b){
int rightX = x + PADDLE_WIDTH;
int bottomY = y + height;
//check if the Ball is between the x values
if(b.getX() > (x - b.getSize()) && b.getX() < rightX){
//check if Ball is between the y values
if(b.getY() > y && b.getY() < bottomY){
//if we get here, we know the ball and the paddle have collided
return true;
}
}
//if we get here, one of the checks failed, and the ball has not collided
return false;
}
Code language: PHP (php)
Note that you must create a getSize method in the Ball method if you have not already done so. The only code changed was the part that checks the left side of the paddle.
if (b.getX() > (x - b.getSize())...
Code language: CSS (css)
Test the new code to make sure the ball bounces off the paddles properly.
Scoring
We’re finally getting close to the end of the project! We need to calculate when either the user or PC misses the ball, update the score, and reset the ball.
Scoring
To determine the score, all we need to do is check if the x value of the ball becomes less than zero (player lost) or greater than the window’s width of 640 (PC lost). Then, we will add one to the opposing person’s score and reset the ball in the center of the screen.
Let’s start by displaying the score. First, declare and initialize int variables userScore and pcScore in the PongGame class. Set them to zero to start.
Updated PongGame instance variable/constructor
public class PongGame extends JPanel implements MouseMotionListener {
static final int WINDOW_WIDTH = 640, WINDOW_HEIGHT = 480;
private Ball gameBall;
private Paddle userPaddle, pcPaddle;
private int userScore, pcScore;
private int userMouseY;
public PongGame() {
gameBall = new Ball(300, 200, 3, 3, 3, Color.YELLOW, 10);
userPaddle = new Paddle(10, 200, 75, 3, Color.BLUE);
pcPaddle = new Paddle(610, 200, 75, 3, Color.RED);
userMouseY = 0;
userScore = 0; pcScore = 0;
//listen for motion events on this object
addMouseMotionListener(this);
}
Code language: PHP (php)
Next let’s display the score on the screen. In the paintComponent method, add a drawString method to print the score near the top of the window. You may format the string however you’d like.
//update score
g.setColor(Color.WHITE);
//the drawString method needs a String to print, and a location to print it at.
g.drawString("Score - User [ " + userScore + " ] PC [ " + pcScore + " ]", 250, 20 );
Code language: JavaScript (javascript)
Test it to make sure it displays the score. You may change the position to center it better.
Next let’s check if someone lost and update the score. In the gameLogic method, check the x position of the gameBall and see if it’s gone past one of the sides.
Here’s the code I added to gameLogic
//check if someone lost
if(gameBall.getX() < 0){
//player has lost
pcScore++;
}
else if(gameBall.getX() > WINDOW_WIDTH){
//pc has lost
userScore++;
}
Code language: JavaScript (javascript)
At this point, you may remove the code that makes the ball bounce off the left and right edges, since we don’t want it bouncing off those edges any more. We only want it to bounce off the top, bottom, and paddles.
Ball.java
public void bounceOffEdges(int top, int bottom){
//if the y value is at the bottom of the screen
if (y > bottom - size){
reverseY();
}
//if y value is at top of screen
else if(y < top){
reverseY();
}
//REMOVE THE PART WITH X
}
Code language: PHP (php)
Finally, let’s have the game reset when someone loses a point.
Create a reset method in the PongGame class. This method will reset the gameBall. To do this, make sure you create setters in the Ball class for x, y, cx, cy, and speed.
PongGame.java
/**
* resets the game to start a new round
*/
public void reset(){
//reset ball
gameBall.setX(300);
gameBall.setY(200);
gameBall.setCx(3);
gameBall.setCy(3);
gameBall.setSpeed(3);
bounceCount = 0;
}
Code language: PHP (php)
Return to the gameLogic method and add the reset code after the score is counted.
//check if someone lost
if(gameBall.getX() < 0){
//player has lost
pcScore++;
reset();
}
else if(gameBall.getX() > WINDOW_WIDTH){
//pc has lost
userScore++;
reset();
}
Code language: JavaScript (javascript)
Optionally, you may pause the game for a second or two after someone loses. A simple way to do this is to put the thread running the game to sleep for a second (1000ms). The sleep method can throw an exception if it gets interrupted, so you may surround it in a try catch block or have the entire method throw an exception. You could achieve similar results by modifying the game timer or the gameLogic method.
public void reset(){
//pause for a second
try{
Thread.sleep(1000);
}
catch(Exception e){
e.printStackTrace();
}
gameBall.setX(300);
gameBall.setY(200);
gameBall.setCx(3);
gameBall.setCy(3);
gameBall.setSpeed(3);
bounceCount = 0;
}
Code language: PHP (php)
Now when you run the game, it should update the score and reset if you miss the ball. It’s still impossible for the PC to lose at this point.
Game Difficulty
The last step in this project is to add a difficulty element. As the game goes on, the ball will increase in speed. This will be based on the number of bounces between the ball and the paddles.
In the PongGame class, declare a new instance variable called bounceCount. It should be an integer set to zero. Initialize it at zero in both the PongGame constructor and the reset method, the same way we created the score and userMouseY variables.
In the gameLogic method whenever a bounce occurs, increment the bounceCount variable by one. Then, create a condition that checks when the bounce count reaches 5 (or any number you select) and resets it to zero. At this point, we will make the ball move faster.
Below is how I updated part of the gameLogic method.
//check if ball collides with either paddle
if(pcPaddle.checkCollision(gameBall) || userPaddle.checkCollision(gameBall)){
//reverse ball if they collide
gameBall.reverseX();
//increase the bounce count
bounceCount++;
}
//after 5 bounces
if (bounceCount == 5){
//reset counter
bounceCount = 0;
//increase speed will go here
}
Code language: JavaScript (javascript)
To increase the speed of the ball, I’ve made an increaseSpeed() method in the Ball class. This will increase the speed of the ball by 1 each time it is called up to a maximum value.
Create a constant (static final) int to store the MAX_SPEED in the Ball class (should go near top after class constructor). I selected a MAX_SPEED of 7.
static final int MAX_SPEED = 7;
Code language: PHP (php)
Now, create the increaseSpeed method in the Ball class. This method checks to make sure the current speed is less than MAX_SPEED. It then must set cx and cy to the new speed and maintain their direction. Remember, cx and cy can be negative. If the ball is going up and to the left, then cx and cy are both negative. If we set it to a positive speed, its direction will reverse as it increases in speed. We want it to continue going the direction it already was going. If cx is -3, we want it to become -4, if cx is 3, we want it to become 4.
To do this, we can divide cx or cy by the absolute value of itself, this will result in positive or negative one. When multiplied by speed, we now have the new change in x or y variables.
public void increaseSpeed(){
//make sure current speed is less than max speed before incrementing
if(speed < MAX_SPEED){
//increase the speed by one
speed ++;
//update cy and cx with the new speed
cx = (cx / Math.abs(cx)*speed);
cy = (cy / Math.abs(cy)*speed);
}
}
Code language: JavaScript (javascript)
Alternatively, you may check if the cx/cy variable is greater or less than zero, then set it equal to the positive or negative speed.
public void increaseSpeed(){
//make sure current speed is less than max speed before incrementing
if(speed < MAX_SPEED){
//increase the speed by one
speed ++;
//alternative way to do it
if(cx > 0){
cx = speed;
}
else if(cx < 0){
cx = speed * -1;
}
if(cy > 0){
cy = speed;
}
else if(cy < 0){
cy = speed * -1;
}
}
}
Code language: JavaScript (javascript)
Return to the PongGame class and call increaseSpeed from the part of the gameLogic method where the fifth bounce was counted.
PongGame.java
//after 5 bounces
if (bounceCount == 5){
//reset counter
bounceCount = 0;
//increase ball speed
gameBall.increaseSpeed();
}
Code language: JavaScript (javascript)
Run the game. After your set number of bounces, the ball should move faster. This will lead to the PC eventually missing the ball and you scoring a point.
Conclusion
Congratulations, you’ve made it to the end of this project. Download the sample project if you have encountered any issues and explore the code. I have commented on it thoroughly. I know the game’s not very fun or challenging yet, but it’s up to you to make it more challenging! I may expand upon this project in the future with additional features. Below are some ideas for you to try for yourself.
Expansion Ideas
- Make the AI more intelligent, so it can hit the higher speed balls more often
- Add keyboard controls
- Make it so two people can play against each other using different keys on one keyboard.
- Randomize the starting location and direction of the ball
- Change the paddle bounce logic so the ball bounces off the paddle at different angles based on where it hit the paddle
- Have the paddles grow or shrink depending on the situation
- Create unique balls with special abilities that randomly spawn, if you hit them, you get some type of special effect or ability
- Make the ball or paddles change colors as its speed increases
- Give the user 3 lives and record their highest score in a list, saved as a file on the user’s hard drive.
- Allow the user to select different difficulty levels or challenges at the beginning of the game
- Give the pong game themes, so it looks like a tennis court or a soccer field
Update: I have begun working on some of these ideas. If you’d like to add some more features to your game, see the next lesson in the series.