In this lab we are going to go back to the start of the video game era and implement a game called Pong. In Pong there are paddles and balls and your job is not to let the ball get past your paddle.
For this lab we will concentrate on a simple single paddle, single ball game. If you write nice general code you should easily be able to scale it up to more advanced versions of the game (like Breakout--- which will be one of the next labs).
To get an idea of how it will look like check this out: Pong.jar | PongL.jar. They are obviously not perfect, and they should not be considered a standard; but they should give you an idea of what to shoot for in this lab.
Your program will consist of three classes:
We will discuss them in turn.
The code for the Pong class is relatively simple. The constructor just makes a new window (like you did in the last lab), adds the appropriate listeners and makes a ball, a paddle and starts the Timer.
public void paint(Graphics g)method. This is already defined for the JFrame class, but for this program you will want to over-ride it and provide your own version of the method. In Java the paint method is called every time Java thinks that the screen needs refreshing (for example when you minimize the window).
One counter-intuitive thing about using paint in Java is how to call it. You don't force the program to paint by directly calling paint (in fact it is a bad idea). You do it by calling the method repaint(). Repaint is actually a Java directive which tells the Java Virtual machine to please call paint as soon as it is convenient. You do not write your own repaint method!
For this program your paint method will be very short: it will clear everything, draw a rectangle to outline the game, then draw the paddle and finally the ball. Since the paint method in class Pong is doing the drawing, it needs to get its information from the other objects (e.g. the Ball and the Paddle). It should get the information for the paddle and the ball from the appropriate instances. E.g.
g.fillOval(ball.getX(), ball.getY(), ball.getSize(), ball.getSize());would draw the ball assuming you have written the ball class correctly. You'll have similar code for the paddle.
Even better, the Ball class would provide a method
void paintBall (Graphics g)and the Pong class would call ball.paintBall(this.getGraphics()) whenever it would need to draw the ball. Note that Pong would need to pass it the Graphics object (canvas) on which to draw. Similar for the paddle.
Note: to clear everything before painting, your first line in paint should be
super.paint(g);This calls paint on the ancesstors of your Pong class, and has the effect that it clears everything out.
private Timer timer;You actually need to be slightly careful about the Timer declaration as there are three different Timer classes in Java. To be on the safe side you might declare it this way:
private javax.swing.Timer timer;
Setting up a timer involves creating a Timer object, registering one or more action listeners on it, and starting the timer using the start method. For example, the following code creates and starts a timer that fires an action event once per MOVE milliseconds (as specified by the first argument to the Timer constructor). The second argument to the Timer constructor specifies a listener to receive the timer's action events.
//timer set to go off every MOVE milliseconds; //the timer events are sent to actionListenerObject---this object must be //an instance of a class that implements ActionListenerInterface timer = new Timer(MOVE, actionListenerObject); timer.setInitialDelay(1000); // Waits a 1000 milliseconds before starting timer.setCoalesce(true); timer.start();The timer in the example will go off every 25 milliseconds and create an event that your program listens for. The class that listens to the timer (the actionListenerObject above) will need an
public void actionPerformed(ActionEvent newEvent)method. Remember that your Pong is implementing the ActionListener interface, so this is the method you need to fulfill that contract. The action that you are listening for in this case is the Timer going off. The action that you want to occur when the Timer goes off is that the ball should move. So your method will tell the ball object to move.
You can, if you need to, add more than one listeners to a timer. If you wanted the ball to listen to the timer as well, you could say something like
timer.addActionListener(ball);In this case the ball would need to implement the ActionListener interface.
Note that in this scenario, Pong listens to the timer and on every timer events calls ball.move(). Another scenario would be for the ball to listen to the timer and move on its own; in this case the ball would have to implement the ActionListener interface. If you chose to implement this other scenario, that is fine, but you need to argue why you prefer it.
The following link may be useful (or not): How to use Swing timers.
Note that Pong tells the paddle to move; however all the logic for figuring out how to move (or whether or not to move) should be in the Paddle class.
Your Paddle should have "getters" for its X and Y values as well as its width and height. It should also have a contains method (more on that later - your Ball class will need it). Finally it should have a move(x, y) method to move it as the mouse moves. Remember, however, that it should not move outside the legal game boundaries.
You might find it useful in implementing the Paddle class to use the Java Rectangle class, or the Rectangle2D.Double class. These classes would be useful for implementing contains, as well as for tracking where the paddle is (using the setLocation(x, y)) method.
The contains method (or alternatively an intersect method) is probably the key part of the program and almost certainly the most difficult. The idea is that other objects should never pierce your paddle, instead they should bounce off it. Among other things this means you need to be able to tell if two objects intersect each other. If you really want to do a nice job with it, it is also useful to figure out where they would hit. There are many strategies for doing this. One thing to think about is that hitting the side of the paddle might be quite different from hitting the top of the paddle. What this suggests is that instead of thinking of your paddle as a single rectangle, you might think of several smaller rectangles (perhaps including Rectangles within the class instead of simply extending the Rectangle class).
In addition to tracking the ball's location you'll also need to keep track of its speed in both the X and Y directions. Each time the move() method is called the ball should move that many pixels in the appropriate direction. The tricky part is that the ball should recognize when it hits either a wall, the ceiling, or the paddle. If it hits a wall its X speed should be reversed. If it hits the ceiling its Y speed should be reversed and if it hits the paddle both should be reversed. Since it needs to figure all of these things out your ball constructor should take in the field dimensions as well as the instance of the Paddle class created in your Pong class. Then when it wants to check if it hit the Paddle it can do so by calling the Paddle's contains (or intersects) method. Alternatively (and perhaps even better) you could have the Ball call a boundary method to check if it is running into something.
Ideally the speed of the ball should be initially set somewhat randomly (e.g. give each dimension a value between 2 and 5). For starters I would just fix them at some constant (I used 3 in each dimension).
One thing to be careful about---when the Ball runs into a paddle (or a wall) it should not literally go into the paddle (its movement should end on the paddle boundary). It's a common error to have Balls get stuck inside paddles.
The ball speed changes that I outline above are merely guidelines. It is reasonable to make the behaviors more interesting. For example the actual part of the paddle you hit might affect the directional change. Or a moving paddle might add velocity, you might change the velocity the longer the program runs, etc. It isn't necessary to do any of this, but you are always encouraged to have fun with your programs.
//position and dimensions of the court private static final int COURT_LEFT = 100; private static final int COURT_TOP = 50; private static final int COURT_HEIGHT = 300; private static final int COURT_WIDTH = 250; // location and dimensions of the paddle private static final int PADDLE_WIDTH = 50; private static final int PADDLE_HEIGHT = 20; private static final int PADDLE_TOP = COURT_TOP + COURT_HEIGHT - PADDLE_HEIGHT -1; private static final int BALLSIZE = 30; private static final int MOVE = 25;