The Breakout Game (Complete Version)
The Breakout Game (Complete Version)
(complete version)
Chapter 11
Overview
• This chapter reviews all the material so far.
• This includes:
– event handling,
– menus,
– inheritance,
– loops,
– the enum construct,
– arrays and ArrayList,
– multi-class solutions,
– and so on.
• This includes moving the ball using a timer, moving the paddle
using the mouse and using keys, destroying bricks when the ball
Overview of the Game
• We will have a Game menu that can be used to start a new
game and pause/restore the current game.
• A Ball Color menu will be used to select the color of the ball.
Possible ball colors will be enumerated in an enum construct.
• A Ball Speed menu will be used to change the speed of the ball.
Possible ball speeds will be enumerated in an enum construct.
• A blue paddle will be displayed a the bottom. Can be moved
with keys or mouse.
• Bricks will be displayed in a grid and destroyed when hit.
• A player life is lost when the ball goes bellow the paddle.
Game Design
• Read over game description and identify nouns. These are
class candidates or enum candidates. For example, see the
red nouns from previous slide.
• We will create super class BreakoutShape and subclasses Ball,
Paddle, and Brick. The reason is that all three classes
represent a rectangular object (or surrounded by a rectangle).
• Note that there are many ways to implement the game. We
just choose one possible way (strive to follow good software
design practices).
The BreakoutShape Class
• Each of the shapes has a rectangle. We will define a
RectangularShape object inside the BreakoutShape class.
RectangularShape is an abstract class and a super class of
both Rectatngle2D and Ellipse2D.
• Every BreakoutShape object will also have a color and a fill.
• We will also include a move method that moves the shape by
the specified amount.
• The setFrame method changes the coordinates of a
RectangularShape. There are also getX, getY, getWidth, and
getHeight methods that return the top left corner and the size
of the rectangular shape.
import java.awt.*;
import java.awt.geom.*;
public class BreakoutShape {
private Color color;
private boolean fill;
private RectangularShape shape;
public BreakoutShape(RectangularShape shape,
Color color, boolean fill) {
this.shape = shape;
this.color = color;
this.fill = fill;
}
public Color getColor() {
return color;
}
public void changeColor(Color color) {
this.color = color;
}
public void draw(Graphics2D g2) {
g2.setPaint(color);
g2.draw(shape);
if (fill) {
g2.fill(shape);
}
}
private int dx = 1;
private int dy = -1;
private BreakoutPanel panel;
public BreakoutFrame() {
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setLocation(100,100);
setSize(WIDTH, HEIGHT);
add(panel);
setResizable(false);
}
}
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class BreakoutPanel extends JPanel {
private javax.swing.Timer timer;
private Ball ball;
public BreakoutPanel() {
ball = new Ball(Color.red,this);
timer = new javax.swing.Timer(10, new ActionListener(){
public void actionPerformed(ActionEvent e) {
ball.move();
repaint();
}
});
timer.start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
ball.draw(g2);
}
}
Adding the Paddle
• The Paddle class will also inherit from the class BreaoutShape.
• The move method will allow us to move the paddle by the
specified amount (paddle can only move left or right).
• We will add a key and mouse listener to the panel to control
the paddle.
• Note that the mouse listener will need to remember the
previous position of the mouse and will move the paddle by
the difference of the current and previous positions.
• We will use the code e.getKeyCode()==KeyEvent.VK_RIGHT
to check if the right arrow on the keyboard is pressed.
public class Paddle extends BreakoutShape {
private static final int START_X = 200;
private static final int START_Y = 430;
private static final int WIDTH = 50;
private static final int HEIGHT = 10;
private static final int SPEED = 10;
private BreakoutPanel panel;
public Paddle(Color color, BreakoutPanel panel) {
super(new Rectangle2D.Double(START_X,
Paddle.START_Y, Paddle.WIDTH, Paddle.HEIGHT),
color, true);
this.panel = panel;
}
---------------------------------------
class BreakoutShape{
...
public boolean intersects(BreakoutShape other) {
return shape.intersects(other.shape.getBounds());
}
...
}
Updating the BreakoutPanel Class
• Before moving the ball, we will create a virtual ball.
• If the virtual ball intersects the paddle, then we will bounce
the ball off the paddle.
• We will also check if the ball hits the left or right side of the
paddle and bounce the ball to the left or right, respectively.
public BreakoutPanel() { ...
public BreakoutPanel(){ ...
timer = new javax.swing.Timer(10, new TimeListener());
}
class TimeListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
Ball newBall = ball.getVirtualBall();
if (newBall.intersects(paddle)) {
ball.goUp();
if (newBall.getX()+newBall.getWidth() / 2 <
paddle.getX() + paddle.getWidth() / 2) {
ball.goLeft();
} else {
ball.goRight();
}
} else if (ball.getY() > //ball bellow paddle
paddle.getY() - paddle.getHeight()) {
System.exit(0);
}
ball.move();
repaint();
} } }
The Player Class
• We will draw a stickman for every life.
• We will start with three lives. Every time a life is lost, a
stickman will be removed from the panel.
• When there are no stickmen, the game is over.
import java.awt.*;
import java.io.File;
import javax.imageio.ImageIO;
public GameMenu() {
super("Game");
JMenuItem startGameMI = new JMenuItem("Start",'S');
startGameMI.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_S, InputEvent.CTRL_MASK));
JMenuItem pauseMI = new JMenuItem("Pause", 'P');
pauseMI.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P,
InputEvent.CTRL_MASK));
JMenuItem quitMI = new JMenuItem("Quit");
startGameMI.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
panel.start();
}
});
pauseMI.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
panel.pause();
}
});
quitMI.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.exit(0);
}});
add(startGameMI);
add(pauseMI);
add(quitMI);
}
}
private class ColorMenu extends JMenu {
public ColorMenu() {
super("Ball Color");
for (BallColor color : BallColor.values()) {
JMenuItem menuItem = new JMenuItem(color.name()+" Ball");
menuItem.addActionListener(new BallColorListener(color));
add(menuItem);
}
}
}
private class BallColorListener implements ActionListener {
private BallColor color;
public void actionPerformed(ActionEvent e) {
panel.changeBallColor(color);
}
public BallColorListener(BallColor color) {
this.color = color;
}
}
private class SpeedMenu extends JMenu {
public SpeedMenu() {
super("Ball Speed");
for (BallSpeed s : BallSpeed.values()) {
JMenuItem menuItem = new JMenuItem(s.name());
menuItem.addActionListener(new BallSpeedListener(
s.speed()));
add(menuItem);
}
}
}
private class BallSpeedListener implements
ActionListener {
private int speed;
public void actionPerformed(ActionEvent e) {
panel.changeBallSpeed(speed);
}
public BallSpeedListener(int speed) {
this.speed = speed;
}
}
}
The BreakoutPanel Class (new revision)
• We will be able to start/stop the game from menus.
• The start method will check if there is a timer. If there is no timer,
then one will be created and the timer will be started. The
method also checks if there are no lives left. If this is the case,
then the lives will be replenished and a brand new game will be
started.
• The pause method will call the stop method on the timer
(assuming there is a timer). The stop method stops the timer.
• We also need to introduce a new variable: gameStarted. It will
keep track if the game is started.
• The changeBallSpeed method will can the setDelay method of
the Timer class to change the frequency of the timer.
public class BreakoutPanel extends JPanel {
private javax.swing.Timer timer;
private Ball ball;
private Paddle paddle;
private Player player;
private boolean gameStarted = false;
...
public void start() {
gameStarted = true;
if (timer != null) {
timer.stop();
}
if (!player.isAlive()) {
player = new Player(); //restart the game
ball = new Ball(Color.RED, this);
}
timer= new javax.swing.Timer(BallSpeed.NORMAL.speed(),
new BreakoutPanel.TimeListener());
timer.start();
repaint();
}
public void pause() {
if (timer == null) {
return;
}
timer.stop();
}
public void changeBallColor(BallColor color) {
ball.changeColor(color.color());
repaint();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
if (!player.isAlive()) {
showMessage("GAME OVER!", g2);
gameStarted = false;
} else {
ball.draw(g2);
paddle.draw(g2);
}
if (gameStarted) {
player.draw(g2);
}
}
public BreakoutPanel() {
ball = new Ball(BallColor.Red, this);
paddle = new Paddle(Color.BLUE, this);
bricks = new ArrayList<>();
player = new Player();
createBricks();
addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
String s = KeyEvent.getKeyText(e.getKeyCode());
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
paddle.moveLeft();
}
if (s.equals("Right")) {
paddle.moveRight();
}
repaint();
}
});
setFocusable(true);
addMouseMotionListener(new MouseMotionAdapter() {
boolean firstTime = true;
int oldX;