25 Assignment 3
25 Assignment 3
occurs, the ball will often bounce back and forth several times
between the top wall and the upper line of bricks without the user
ever having to worry about hitting the ball with the paddle. This
condition is a reward for “breaking out” and gives meaning to the
name of the game. The diagram on the right shows the situation
shortly after the first ball has broken through the wall. That ball
will go on to clear several more bricks before it comes back down
the open channel.
It is important to note that, even though breaking out is a very
exciting part of the player’s experience, you don’t have to do
anything special in your program to make it happen. The game is
simply operating by the same rules it always applies: bouncing off
walls, clearing bricks, and otherwise obeying the laws of physics.
The number, dimensions, and spacing of the bricks are specified using named constants
in the starter file, as is the distance from the top of the window to the first line of bricks.
The only value you need to compute is the x coordinate of the first column, which should
be chosen so that the bricks are centered in the window, with the leftover space divided
equally on the left and right sides. The color of the bricks remain constant for two rows
and run in the following rainbow-like sequence: RED, ORANGE, YELLOW, GREEN, CYAN.
–3–
/*
* File: Breakout.java
* -------------------
* This file will eventually implement the game of Breakout.
*/
import acm.graphics.*;
import acm.program.*;
import acm.util.*;
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class Breakout extends GraphicsProgram {
/** Width and height of application window in pixels */
public static final int APPLICATION_WIDTH = 400;
public static final int APPLICATION_HEIGHT = 600;
/** Dimensions of game board (usually the same) */
private static final int WIDTH = APPLICATION_WIDTH;
private static final int HEIGHT = APPLICATION_HEIGHT;
/** Dimensions of the paddle */
private static final int PADDLE_WIDTH = 60;
private static final int PADDLE_HEIGHT = 10;
/** Offset of the paddle up from the bottom */
private static final int PADDLE_Y_OFFSET = 30;
/** Number of bricks per row */
private static final int NBRICKS_PER_ROW = 10;
/** Number of rows of bricks */
private static final int NBRICK_ROWS = 10;
/** Separation between bricks */
private static final int BRICK_SEP = 4;
/** Width of a brick */
private static final int BRICK_WIDTH =
(WIDTH - (NBRICKS_PER_ROW - 1) * BRICK_SEP) / NBRICKS_PER_ROW;
/** Height of a brick */
private static final int BRICK_HEIGHT = 8;
/** Radius of the ball in pixels */
private static final int BALL_RADIUS = 10;
/** Offset of the top brick row from the top */
private static final int BRICK_Y_OFFSET = 70;
/** Number of turns */
private static final int NTURNS = 3;
}
–4–
Here’s a suggestion: Why don’t you get this part of the program
working by Wednesday the 31st, so that you can produce just the
diagram at the right? The display has most of what you see on the
final screen and will give you considerable confidence that you
can get the rest done. And you’ll be well on your way before time
gets short.
The challenge in creating the paddle is to make it track the mouse. The technique is
similar to that discussed in Chapter 9 for dragging an object around in the window. Here,
however, you only have to pay attention to the x coordinate of the mouse because the y
position of the paddle is fixed. The only additional wrinkle is that you should not let the
paddle move off the edge of the window. Thus, you’ll have to check to see whether the x
coordinate of the mouse would make the paddle extend beyond the boundary and change
it if necessary to ensure that the entire paddle is visible in the window.
Here’s a soon-to-become-boring suggestion: Why don’t you get this part of the program
working by Friday the 2nd, so that you can follow the mouse with the paddle? This entire
part of the program takes fewer than 10 code lines, so it shouldn’t take so long. The hard
part lies in reading the Graphics chapter and understanding what you need to do.
Create a ball and get it to bounce off the walls
At one level, creating the ball is easy, given that it’s just a filled GOval. The interesting
part lies in getting it to move and bounce appropriately. You are now past the “setup”
phase and into the “play” phase of the game. To start, create a ball and put it in the center
of the window. As you do so, keep in mind that the coordinates of the GOval do not
specify the location of the center of the ball but rather its upper left corner. The math is
not any more difficult, but may be a bit less intuitive.
The program needs to keep track of the velocity of the ball, which consists of two
separate components, which you will presumably declare as instance variables like this:
private double vx, vy;
The velocity components represent the change in position that occurs on each time step.
Initially, the ball should be heading downward, and you might try a starting velocity of
+3.0 for vy (remember that y values in Java increase as you move down the screen). The
game would be boring if every ball took the same course, so you should choose the vx
component randomly. We’ll talk about random numbers later in the quarter, but for now
you should simply do the following:
1. Declare an instance variable rgen, which will serve as a random-number generator:
–5–
Once you’ve gotting things started, your next challenge is to get the ball to bounce
around the world, ignoring the paddle and the bricks entirely. To do so, you need to
check to see if the coordinates of the ball have gone beyond the boundary, taking into
account that the ball has a nonzero size. Thus, to see if the ball has bounced off the right
wall, you need to see whether the coordinate of the right edge of the ball has become
greater than the width of the window; the other three directions are treated similarly. For
now, have the ball bounce off the bottom wall so that you can watch it make its path
around the world. You can change that test later so that hitting the bottom wall signifies
the end of a turn.
Computing what happens after a bounce is extremely simple. If a ball bounces off the top
or bottom wall, all you need to do is reverse the sign of vy. Symmetrically, bounces off
the side walls simply reverse the sign of vx.
Checking for collisions
Now comes the interesting part. In order to make Breakout into a real game, you have to
be able to tell whether the ball is colliding with another object in the window. As
scientists often do, it helps to begin by making a simplifying assumption and then
relaxing that assumption later. Suppose the ball were a single point rather than a circle.
In that case, how could you tell whether it had collided with another object?
If you look in Chapter 9 at the methods that are defined at the GraphicsProgram level,
you will discover that there is a method
public GObject getElementAt(double x, double y)
that takes a position in the window and returns the graphical object at that location, if
any. If there are no graphical objects that cover that position, getElementAt returns the
special constant null. If there is more than one, getElementAt always chooses the one
closest to the top of the stack, which is the one that appears to be in front on the display.
What happens if you call
getElementAt(x, y)
where x and y are the coordinates of the ball? If the point (x, y) is underneath an
object, this call returns the graphical object with which the ball has collided. If there are
no objects at the point (x, y), you’ll get the value null.
–6–
So far, so good. But, unfortunately, the ball is not a single point. It occupies physical
area and therefore may collide with something on the screen even though its center does
not. The easiest thing to do—which is in fact typical of the simplifying assumptions
made in real computer games—is to check a few carefully chosen points on the outside of
the ball and see whether any of those points has collided with anything. As soon as you
find something at one of those points, you can declare that the ball has collided with that
object.
In your implementation, the easiest thing to do is to check the four corner points on the
square in which the ball is inscribed. Remember that a GOval is defined in terms of its
bounding rectangle, so that if the upper left corner of the ball is at the point (x, y), the
other corners will be at the locations shown in this diagram:
(x, y) (x + 2r, y)
These points have the advantage of being outside the ball—which means that
getElementAt can’t return the ball itself—but nonetheless close enough to make it
appear that collisions have occurred. Thus, for each of these four points, you need to:
1. Call getElementAt on that location to see whether anything is there.
2. If the value you get back is not null, then you need look no farther and can take that
value as the GObject with which the collision occurred.
3. If getElementAt returns null for a particular corner, go on and try the next corner.
4. If you get through all four corners without finding a collision, then no collision exists.
It would be very useful to write this section of code as a separate method
private GObject getCollidingObject()
that returns the object involved in the collision, if any, and null otherwise. You could
then use it in a declaration like
GObject collider = getCollidingObject();
which assigns that value to a variable called collider.
From here, the only remaining thing you need to do is decide what to do when a collision
occurs. There are only two possibilities. First, the object you get back might be the
paddle, which you can test by checking
if (collider == paddle) . . .
If it is the paddle, you need to bounce the ball so that it starts traveling up. If it isn’t the
paddle, the only other thing it might be is a brick, since those are the only other objects in
the world. Once again, you need to cause a bounce in the vertical direction, but you also
need to take the brick away. To do so, all you need to do is remove it from the screen by
calling the remove method.
Finishing up
If you’ve gotten to here, you’ve done all the hard parts. There are, however, a few more
details you need to take into account:
–7–
• You’ve got to take care of the case when the ball hits the bottom wall. In the prototype
you’ve been building, the ball just bounces off this wall like all the others, but that
makes the game pretty hard to lose. You’ve got to modify your loop structure so that it
tests for hitting the bottom wall as one of its terminating conditions.
• You’ve got to check for the other terminating condition, which is hitting the last brick.
How do you know when you’ve done so? Although there are other ways to do it, one
of the easiest is to have your program keep track of the number of bricks remaining.
Every time you hit one, subtract one from that counter. When the count reaches zero,
you must be done. In terms of the requirements of the assignment, you can simply
stop at that point, but it would be nice to give the player a little feedback that at least
indicates whether the game was won or lost.
• You’ve got to experiment with the settings that control the speed of your program.
How long should you pause in the loop that updates the ball? Do you need to change
the velocity values to get better play action?
• You’ve got to test your program to see that it works. Play for a while and make sure
that as many parts of it as you can check are working. If you think everything is
working, here is something to try: Just before the ball is going to pass the paddle level,
move the paddle quickly so that the paddle collides with the ball rather than vice-versa.
Does everything still work, or does your ball seem to get “glued” to the paddle? If you
get this error, try to understand why it occurs and how you might fix it.
Possible extensions
This assignment is perfect for those of you who are looking for + or (dare I say it) ++
scores, because there are so many possible extensions. Here are a few:
• Add sounds. The version that is running as an applet on the CS 106A assignment page
plays a short bounce sound every time the ball collides with a brick or the paddle. This
extension turns out to be very easy. The starter project contains an audio clip file
called bounce.au that contains that sound. You can load the sound by writing
AudioClip bounceClip = MediaTools.loadAudioClip("bounce.au");
–8–