Breakout
Revised July 2003
Overview
In this lab, you will get to know some key parts of the java AWT, namely painting and containment. Polymorphism also plays a small role, unless you wish to deal with it in more detail.
Besides the code and javadoc linked from the problem sets page, Breakout has the following resources:
Intro/Prelab
We're going to be writing part of the code for a Breakout game. Specifically, the code that defines how a particular brick behaves.
If you're not familiar with the game Breakout, the basic idea is this: You're in a box that contains a paddle, a ball, and rows of bricks. You can move the paddle from side to side, but not up or down, and it is your only method of controlling the movement of the ball. The object of the game is to use the ball to destroy all of the bricks, without letting the ball hit the ground. [img: screenshot of breakout]
In our version of Breakout, we have defined several different sets of behavior for you. Every game object(balls, bricks, etc) implements the BreakoutComponent interface. Most objects do this via a sub-interface that more specifically classifies what kind of object it is. For example, the Brick interface is a subclass of BreakoutComponent that exists so that the Board can tell whether you've won the game(cleared all the Brick objects) or not.
Here is a brief summary of the Breakout interfaces:
BreakoutComponent
- all game objects implement this interface in some fashion.
Rebounding
- all game objects that need to do rebound checks(ie, moving objects that bounce off of things) are required to implement this interface.
Brick
- BreakoutComponent - When the Board doesn't have any Brick objects left, you have won the game. No different than BreakoutComponent.
Ball
- BreakoutComponent and Rebounding - When the Board doesn't have any Ball objects left, you have lost the game. Every tick, a Ball object checks to see if it has hit anything, and if it has, calls that BreakoutComponent's hit method using itself as the argument. Adds no additional functionality to the BreakoutComponent and Rebounding interfaces.
Wall
- BreakoutComponent - A generally benign object that serves to keep the Ball inside the visible space. A Wall whose killing()
method returns true(generally the Wall acting as the floor) has the added functionality that when it is hit, it hits back. Has added functionality to the standard BreakoutComponent by way of the killing()
method.
Paddle
- BreakoutComponent - Used to keep a Ball object from hitting the floor. No different than BreakoutComponent.
Okay, so on to the meatier bits of how Breakout works.
You have a Board. A Board keeps track of all the game objects(the Bricks, Balls, etc) but the Board doesn't really know the difference between a brick, a ball, a wall, or a paddle -- it just has a list of generic BreakoutComponent objects. The BreakoutComponent interface guarantees that everything on the Board will be able to answer some basic queries about what size it is, where it is on the board, whether it's an ellipse or a rectangle or whatever, and some other things that you can look at on the BreakoutComponent javadoc if you wish. One of the more important methods it defines in the context of this lab, though, is paint
.
paint
is a funky sort of method. Unlike most of the methods you've seen so far, you the programmer are not the one who decides when and where this method is called. In fact, you'd probably find it pretty difficult to call this method explicitly on any BreakoutComponent, even if you tried, because you're missing the key ingredient: a Graphics
. A Graphics
object is what does most of the work in a paint
method -- it knows how to draw ovals, rectangles, lines, text, and even images. But unfortunately, you can't create a Graphics
out of thin air like you might do new String()
or new Frame()
. Go ahead, check the javadoc. The constructor for Graphics is protected: you're not allowed to create one directly, which means you can't call paint
.
Who, then, does call paint
?! This is what makes paint
so neat. Java itself decides when paint
gets called. paint
happens any time a window, or part of a window, needs to be painted -- like when it first appears, or when you momentarily drag something over top of it, and drag it away again.
So... what happens if you want something to be animated? If you can't tell your component to paint
, how do you change what shows up on the screen after it paints it the first time?
It would be really aggravating if the answer to this question was, "You don't. Tough." But then, people probably wouldn't use Java for as many entertaining things as they do. Luckily, all components have a nifty "hidden" method called repaint()
that Java handles all by itself -- you don't have to write a word of code for it. When you call repaint
on a Component (a java.awt.Component
, not a BreakoutComponent -- sadly, BreakoutComponent is only a shadowy simulation of what a Component ought to be) Java knows to go and find the Graphics it used to paint this Component last time, and uses the Component's paint
method to see how it ought to be painted. repaint()
is like the Component saying, "Hey! I changed! I need to be painted again!" just like what happens when you cover up the Component for a bit and then uncover it again.
In Breakout, we don't really need to worry about repaint()
(though certainly don't forget what you just learned!). In Breakout, the Board is getting automatically repainted every few milliseconds. World
, the object that controls the "clock" for Breakout, knows that things are moving all the time, and is constantly running a loop telling everything on the board to update themselves, and telling the GUI components to repaint(which, in turn, tell all the BreakoutComponents they know about to repaint as well). Part of the target exercise of this lab is to understand the basics of writing the paint(Graphics g) method of a Brick object.
To further prepare for the lab, you should familiarize yourself with the BreakoutUI, Board, and BoardPanel classes, and how they interact.
Questions
BreakoutComponent's paint(Graphics g)
method:
- What methods are available to you in Graphics that let you draw things on the screen?
BreakoutComponent's hit(bc)
method:
- What kind of information can you get about the BreakoutComponent that just hit your Brick?
- What are some ways you could use this information to change the behavior or appearance of your Brick after being hit? (To more fully answer this question, you might be interested in checking out the documentation for the java classes java.awt.Dimension and java.awt.Point.)
- What information does the ball receive from your Brick by calling its hit method?
- (Bonus) The Wall interface adds a method that tells whether things should die when they hit it -- killing() -- that lets a particular Wall act as the floor. A ball dies when it is hit by a Wall whose killing() method returns true. How might you create a Brick that also kills balls when hit?
Setting up Breakout:
- How do you create a BreakoutUI? A Board?
- How do you add a Brick to a Board?
- What else do you have to do to get a Board to show up in the Breakout window?
The Lab
One
Getting started with Main
The first thing you need to do is set up a file called Main.java, which will define a class with a special method that starts the whole thing running.
- Create a file named Main.java and add it to your project. Make it part of the breakout package by putting
package breakout;
at the top of the file.
- The only method you need in the Main class is the "main" method:
public static void main(String[] args) {}
- Put something inside the main method to check and see if we've set things up correctly. If you want to be boring and get on with the rest of the lab, Hello World is quick and easy, and your next step is here. The rest of us are going to try something more interesting:
boolean running=true;
while(running) {
String instruction =
javax.swing.JOptionPane.showInputDialog("Now what?");
if("stop".equals(instruction)) {
running = false;
}
}
Alright, a quick explanation:
We basically created a while loop that keeps bringing up a dialog ask box until you answer "stop". javax.swing
is a java package that does a lot of similar things to java.awt
, but with lots more cool features -- like JOptionPane. JOptionPane is a really nifty tool that does a lot of the simpler dialog boxes and option panes automatically, like the ask box we're using here. showInputDialog
brings up a dialog box with a prompt and a place for the user to enter text. The String you pass into the method becomes the prompt, and the String the method returns is whatever it gets from the user. We test if that text is the String "stop"
, and if it is, we stop, and if it isn't, we keep asking until the user complies. :-)
Set breakout.Main
as the target of this project, and run it. It should bring up the pretty little ask box, and keep bothering you with it until you enter "stop".
Two
Set up a BreakoutUI
- In order to get Breakout to appear, we need to create an instance of
BreakoutUI
, the user interface engine for Breakout. Change the body of the main method so that it creates a new BreakoutUI
object using the no-args constructor.
- Run Breakout. You should see a nice little window with some text telling you to load a
Board
. Click the load button to select a boardfile from the boards directory and play some breakout... you know you want to.
Three
Building a Brick
Everything that you see on the breakout Board
implements an interface called BreakoutComponent
. BreakoutComponent
defines some basic functionality such as location getters/setters, painting behavior, and ingame activity.
Take a look at the javadoc for BasicBrick
. First check out the interface it implements -- Brick
. Brick
is an interface that extends BreakoutComponent
without adding any functionality, and basically just lets the Board
know that this particular BreakoutComponent
is a Brick
. Why this is useful: when all the Brick
objects in the Board
are gone, you've won the game.
- Create a new
Brick
class that extends BasicBrick
. For now, the only methods we're going to write are a constructor and a new paint
method.
- Write a constructor that passes a width and a height to the super-constructor. You may have noticed in the javadoc that
BasicBrick
doesn't have a no-args constructor. If we don't explicitly call the super-constructor like this, java will try to automatically call a no-args constructor on BasicBrick
, which doesn't exist(and we can't have that). You can choose whether or not your new brick's constructor takes a width and a height, or whether it doesn't take any arguments and just calls the superconstructor with some default size in mind.
- Write a
paint
method. This is what the UI will use to draw the brick. For now, do something basic, like
g.drawRect(0, 0, this.size.width, this.size.height);
This will draw the outline of a rectangle at the brick's location [Note: the (0,0) is with respect to this brick, not the whole panel] with the same width and height as this Brick
. this.size
is a field belonging to BasicBrick
-- but since it is protected
instead of private
, we can use it directly here.
For now, let's get our Brick
into a board.
Four
Adding our Brick
to a Board
Breakout uses a class called Board
to keep track of all the game pieces -- bricks, balls, paddles, and walls. When we clicked "Load" before, Breakout looked in a configuration file to get the information it needed to build and load a new Board
object. We're going to use a simpler process to build our first board.
- First, take a look at the javadoc for
Board
. Notice that it can either use a no-args constructor, or take a BoardPanel
object as a constructor argument. A BoardPanel
is the little section of the BreakoutUI
that the Board
gets painted on, and Board
needs to know how big it is before it will let you add bricks to it. Lucky for us, if you peek at the javadoc for BreakoutUI
, a way to get ahold of the right BoardPanel
object is provided. In your main method, create a new Board
object using the BoardPanel
you get from BreakoutUI.getBoardPanel()
.
- Now we can use one of
Board
's add methods to add a new instance of our Brick
to the Board
. There are two choices; one takes just a BreakoutComponent
. and the other takes a BreakoutComponent
and a Point
. The latter lets you place bricks exactly where you want them; the former utilizes a rudimentary layout manager to arrange bricks on a grid based on the largest Brick
in the Board
. Since our new Brick
extends BasicBrick
, it is already a BreakoutComponent
. and can just be added directly. Pick one of the two add methods, and add your Brick
to the Board
.
- Run breakout to see what we've done. augh! our
Brick
isn't there! Well, that's because we didn't load our Board
into the UI. We created a Board
. but we didn't do a key part of the java that happens when you click the "Load" button. If you take a look at the javadoc for BreakoutUI
(again...) you may notice a conspicuous-looking method called load
. Use this method to load the Board
into the BreakoutUI
we're using.
- Now try running breakout. Your
Brick
should appear in the upper left corner of the Board
. and the text at the top of the frame should be telling you to press "Start" instead of "Load".
Five
Because breakout is less fun without a paddle & a ball
Let's make our new Board
actually playable.
- Conveniently,
Board
provides two methods, addDefaultWalls()
, and addDefaultBallPaddle()
, that will fill out the rest of the pieces necessary for breakout. Call these methods on your Board
before you load it into the UI, and this time when you run breakout, you can click Start, and actually play.
Six
Because a flat black rectangle is boring
Go look at the javadoc for java.awt.Graphics
, and see what kinds of things you can draw with it. How would you draw a solid brick? A solid brick with a different colored outline? A brick with... polka dots? Stripes? The Olin "O"? Play around, create a couple different Brick
classes that look neat. Add multiple instances of different bricks to the Board
, and use them all together.
You can also override other BasicBrick
methods, if you like. Most notably, the behavior that happens when a brick is hit by a ball:
boolean hit(BreakoutComponent)
This method tells the Brick
which BreakoutComponent
just hit it. It should return true if the Brick
died and needs to be removed from the Board
, and false if it should stay put. Experiment with bricks that don't die unless you hit them several times, or maybe even bricks that spawn new bricks -- after a game has started, you can use Board.add(BreakoutComponent, Point)
to add components to the Board
. (auto-layout-add is disabled while in-game)
Once you have completed a few brick classes that draw themselves different ways, you have completed the target exercise for this lab. If you have extra time, though, there's always...
Gravy!
The polymorphism features in breakout are terribly entertaining, so you can definitely start there. Remember that since all the members of the board have to implement BreakoutComponent
, they all have a set of common methods that you can use regardless of what class they might be from -- that's what polymorphism means, really: using interfaces to give several different types of objects a guaranteed similar behavior, so you can treat them all as a single group. This could definitely be useful, for example, in doing neat things with the hit(BreakoutComponent bc)
method.
If you get bored with playing with bricks though, or if you're getting annoyed with having to maintain 20 add()
statements, you'll probably be interested in how to write a Board
file -- like the one we loaded at the beginning.
Boardfiles
Check out the format of the exampleboard.brd
file in the Board
directory. It's massively documented, but should you need a hand figuring something out, don't hesitate to ask someone. Try duplicating whatever Board
you've got set up in your main method using a boardfile, run it, and load it.
Okay, so you just managed to achieve something you'd already had five minutes ago... great. But now, it's really easy to change up the ball and paddle, and keep track of large numbers of bricks in the Board
with fewer keystrokes.
The Rebounding Interface
The tricky thing about moving objects like balls is that they have to figure out what to do when they hit something. This behavior is defined in the Rebounding
interface. Luckily, the functionality you need for things that bounce is already covered for you in BasicBall
, so you're probably alright just extending that class. If you want to make something that moves, but doesn't need to rebound off of anything, you probably don't need to implement Rebounding
-- just put the movement in the update()
method.
Some Ideas(not that you creative people really need any)
- Remember ballworld? Something tells me we can apply this here...
- Adjustable velocity balls
- Flying bricks!
- Paddles with ammunition
- A round paddle -- so you can aim the ball better
- etc, etc. Impress your friends.