View RSS Feed

sunde887

Breakout Tutorial

Rate this Entry
by , 10-21-2011 at 12:32 AM (6655 Views)
The first step in designing something is to think it through. What pieces should it have and how should they work together? What methods, and instance variables should a class have? Let’s start by naming the classes we will need for brick breaker (or breakout/whatever you want to name it).

We will need a paddle which the player controls, a ball which moves around the screen, and the bricks which are meant to be broken. There will also be a canvas class which everything is drawn upon. I plan to also provide some ideas for changes that I believe would be beneficial for you to try.
Let’s start with the paddle, what should it do? The paddle seems simple, it simply moves along some horizontal line. This paddle will also be able to draw itself, and check if a ball hits it. The first thing the class needs however; is the ability to store its location on the screen. Since the paddle moves horizontally, the only changeable variable will be the x position. Here is the full code for the Paddle class, and below it is an explanation of the entire class:
Java Code:
import java.awt.Color;
import java.awt.Graphics2D;

public class Paddle {
	public static final int Y_POS = Canvas.HEIGHT - 30;
	public static final int PADDLE_WIDTH = 80;
	public static final int PADDLE_HEIGHT = 10;
	public static final Color PADDLE_COLOR = Color.black;
	private int xPos;
	public static final int DELTA_X = 5;
	private int score;
	private int lives;
	
	public Paddle(int xPos){
		this.xPos = xPos;
		score = 0;
		lives = 5;
	}
	
	public void setX(int xPos){ 
		this.xPos = xPos;
		if(xPos < 0) this.xPos = 0;
		if(xPos > (Canvas.WIDTH - PADDLE_WIDTH)) this.xPos = (Canvas.WIDTH - PADDLE_WIDTH);
	}
	
	public int getX(){ return xPos; }
	public int getScore(){ return score; }
	public void setScore(int score){ this.score = score; }
	public int getLives(){ return lives; }
	public void setLives(int lives){ this.lives = lives; }
	
	/**
	 * Determines whether a Ball has hit the paddle
	 * 
	 * Checks if ball is in range of the paddles x's first. If
	 * it ball.x > paddle.x but less than paddle.x + PADDLE_WIDTH + 15
	 * it is in the horizontal range. 
	 * 
	 * Next checks if it is actually hitting the paddle. if ball.y + the diameter
	 * is close to the paddles y position the method returns true. Has a final
	 * condition which gives somewhat of an error buffer to make sure the method
	 * doesn't allow the ball to skip over the threshhold and thus go right through
	 * the paddle
	 * 
	 * @param b
	 * @return
	 */
	public boolean hitPaddle(Ball b){
		if(b.getX() <= xPos + (PADDLE_WIDTH + 15)){
			if(b.getX() >= xPos - 10){
				if((b.getY() + (Ball.DIAMETER - 1)) >= (Y_POS)){
					if((b.getY() + (Ball.DIAMETER - 1)) <= (Y_POS + (PADDLE_HEIGHT - 5))){
						return true;
					}
				}
			}
		}
		return false;
	}
	
	public void drawPaddle(Graphics2D g){
		g.setColor(PADDLE_COLOR);
		g.fillRect(xPos, Y_POS, PADDLE_WIDTH, PADDLE_HEIGHT);
		g.setColor(Color.gray);
		g.drawRect(xPos, Y_POS, PADDLE_WIDTH, PADDLE_HEIGHT);
	}
	
	public static void main(String[] args){
		Ball b = new Ball(110, (Y_POS - (Ball.DIAMETER - 5)), 5, 5);
		Paddle p = new Paddle(110);
		for(int i = 1; i <= PADDLE_WIDTH; ++i){
			b.setX(b.getX() + 1);
			System.out.println(p.hitPaddle(b));
		}
		System.out.println(p.hitPaddle(b));
	}
}
Now this class is reasonably simple. It starts with some constants (anything in capital letters). The constants basically say things about the class. The name of the constant generally will give you an understanding of what each does. Just in case I will explicitly describe them here:
• Y_POS : This is the Y position where the paddle will be located. The paddle only moves horizontally, so this remains constant.
• PADDLE_WIDTH: This is how wide the paddle will be. Since the paddle is being represented with the (x, y) position being the upper left hand corned. The width is how far to the right the paddle stretches. If the upper left corner is (x, y), then the upper right hand corner will be (x+PADDLE_WIDTH, y).
• PADDLE_HEIGHT: How tall the paddle is. If (x, y) is the top left point, then (x, y+PADDLE_HEIGHT) is the bottom left corner, and (x+PADDLE_WIDTH, y+PADDLE_HEIGHT) is the bottom right corner.
• PADDLE_COLOR: No real explanation for this constant, it’s simply the color of the paddle
• DELTA_X: This is how much in the x position to move the xPos variable (this isn’t really needed).
Next, the instance variables. These dictate things about the class. Briefly, the xPos shows where the xPos in the (x,y) of the upper left hand corner—this changes. Score is the score the player has earned from breaking bricks (gain points), or missing the ball (lose points).
This class has basic getters and setters which either set, or get a value to allow other classes to manipulate the paddle class. The class has a method which knows how to draw itself, given a Graphics2D object. One final important method is the one which really allows you to play the game. It takes as input some Ball, and determines whether or not the ball has hit the paddle. If the ball hits the paddle, it returns true, if not it returns false. The comment provides a more detailed explanation of how this method works. Most of the bounds detection takes a similar approach where it just checks first if the ball is hitting the object on the bottom or top, and then determines whether it was the top or bottom based on the relation of the y positions of the ball and the object.

That’s it for the paddle class. For the most part a very simple class, next lets look at the ball class.

Java Code:
import java.awt.Color;
import java.awt.Graphics2D;

public class Ball {
	private int xPos, yPos;
	private int dX, dY;
	public static final int DIAMETER = 15;
	public static final Color BALL_COLOR = Color.black;
	
	public Ball(int xPos, int yPos, int dX, int dY){
		this.xPos	= xPos;
		this.yPos	= yPos;
		this.dX 	= dX;
		this.dY		= dY;
	}
	
	public void setX(int xPos){	this.xPos 	= xPos;	}
	public void setY(int yPos){	this.yPos	= yPos;	}
	public void setDX(int dX){	this.dX 	= dX;	}
	public void setDY(int dY){	this.dY		= dY;	}
	public int getX(){	return xPos;	}
	public int getY(){	return yPos;	}
	public int getDX(){	return dX;		}
	public int getDY(){	return dY;		}
	
	public void move(){
		xPos += dX;
		yPos += dY;
	}
	
	public void drawBall(Graphics2D g){
		g.setColor(BALL_COLOR);
		g.fillOval(xPos, yPos, DIAMETER, DIAMETER);
		g.setColor(Color.gray);
		g.drawOval(xPos, yPos, DIAMETER, DIAMETER);
	}
}
The ball class is actually easier than the paddle class. The ball class has instance variables for it’s location which are similar to that of the paddle class. It also has delta x and delta y (dX and dY). These two variables represent how much in the x or y the ball moves for each time unit. The move method actually makes the ball move one dX and one dY each time it is called. For an example, let’s say the ball starts at location 100, 100. If dX and dY are both 5, then after one call to move the ball will be located at 105, 105. This class has a couple of constants as well:
• DIAMETER: Simple enough, the diameter of the ball, the distance from one end to the other
• BALL_COLOR: The color of the ball
The ball class also has getters and setters which aren’t going to be given much of an explanation, I will assume people are familiar with what getters and setters are, if you are not, read here: http://download.oracle.com/javase/tutorial/java/concepts/object.html

Next lets look at the brick class, and the final class needed besides the drawing canvas.
Java Code:
 
import java.awt.Color;
import java.awt.Graphics2D;

public class Brick {
	public static final int BRICK_WIDTH = 60;
	public static final int BRICK_HEIGHT = 20;
	private int 	xPos, yPos;	
	private Type 	brickType;
	
	enum Type{
		ULTRA	(6, 700, Color.black),
		HIGH	(3, 150, Color.RED), 
		MEDIUM	(2, 100, Color.BLUE),
		LOW		(1, 50, Color.GREEN),
		DEAD	(0, 0, Color.WHITE);
		private int life;
		private Color color;
		private int points;
		
		Type(int life, int points, Color color){
			this.life = life;
			this.points = points;
			this.color = color;
		}
		public int getPoints(){	return points;	}
		public Color getColor(){	return color;	}
		public int getLife(){	return life;	}
	}
	
	public Brick(int xPos, int yPos, Type brickType){
		this.xPos = xPos;
		this.yPos = yPos;
		this.brickType = brickType;
	}
	
	public int getX(){	return xPos;	}
	public int getY(){	return yPos;	}
	public Type getBrickType(){	return brickType;	}
	
	public boolean hitBy(Ball b){
		//first check if it hits from the bottom or top
		if(b.getX() <= (xPos + BRICK_WIDTH) && b.getX() >= xPos){
			//hit bottom
			if(b.getY() <= (yPos + BRICK_HEIGHT) && b.getY() >= (yPos + (BRICK_HEIGHT / 2))){
				b.setDY(b.getDY() * -1);
				return true;
			}
			//hit top
			else if(b.getY() >= (yPos - Ball.DIAMETER) && b.getY() < (yPos + (Ball.DIAMETER / 3))){
				b.setDY(b.getDY() * -1);
				return true;
			}
		}
		//determines if it from a side
		else if(b.getY() <= (yPos + BRICK_HEIGHT) && b.getY() >= yPos){
			//hit right
			if(b.getX() <= (xPos + BRICK_WIDTH) && b.getX() > (xPos + (BRICK_WIDTH - (Ball.DIAMETER / 2)))){
				b.setDX(b.getDX() * -1);
				return true;
			}
			//hit left
			else if(b.getX() >= (xPos - Ball.DIAMETER) && b.getX() < (xPos + (Ball.DIAMETER / 2))){
				b.setDX(b.getDX() * -1);
				return true;
			}
		}
		return false;
	}
	
	public void decrementType(){
		switch(brickType.life){
			case 6:
			case 5:
			case 4:
				--brickType.life;
				break;
			case 3:
				brickType = Type.MEDIUM;
				break;
			case 2: 
				brickType = Type.LOW;
				break;
			case 1:
			default:
				brickType = Type.DEAD;
				break;
		}
	}
	
	public void drawBrick(Graphics2D g){
		g.setColor(Color.white);
		g.fillRect(xPos, yPos, BRICK_WIDTH, BRICK_HEIGHT);
		g.setColor(brickType.color);
		g.fillRect((xPos+2), (yPos+2), BRICK_WIDTH-4, BRICK_HEIGHT-4);
		g.setColor(Color.black);
		g.drawRect((xPos+2), (yPos+2), BRICK_WIDTH-4, BRICK_HEIGHT-4);
	}

	public boolean dead() {
		if(brickType.life == 0)
			return true;
		return false;
	}
}
This class is a bit more complex than the previous two classes. First let’s talk about the constants this class has:
• BRICK_WIDTH: How wide the brick is, see the PADDLE_WIDTH constant
• BRICK_HEIGHT: How high the brick is, see the PADDLE_HEIGHT constant
The brick has an (x,y) position similar to the paddle, except the brick does not move. Next is an enum called BrickType which is nested inside the class. The enum, like any enum has some constants which represent how hard it is to break some type of brick, and it also supplies it with a point amount to award and a color for the brick. Most of this is pretty self-explanatory. Each time the brick is hit, the points for the type are awarded to the player. This enum also contains methods which allow you to view the variables of the type. For the most part I believe the enum is very self-explanatory and I will not get too in depth. If you have further questions about enums, please ask them in the comments. The decrement type method is fairly simple and is to be called each time a brick is hit; this method drops from one type down to the next by using a simple switch statement. The logic is simple; it gets the life assigned with the type, and uses a switch statement on it. It then switches down to the next type based on the previous type. If the original type is MEDIUM, it moves to LOW, if it is HIGH, it drops down to MEDIUM. The Type enum also offers a DEAD brick which lets you know when to stop drawing a specific brick. There is a method which determines if you have killed a brick. This class also knows how to draw itself and the draw method is a bit more interesting the then previous classes. It contains a white rim, with a black edge to make different bricks easily distinguishable. It does this by first drawing a white filled rectangle, and then a slightly smaller colored filled rectangle, and finally a regular black rectangle.

Now to finally hit on a more serious method, this method is a bit more complex (albeit not much more). It is responsible for a lot of the game. This is the method which determines if the ball hits the block, and also decides which side of brick was hit. The method I am speaking of is the hitBy method. It takes a Ball in as an argument and makes comparisons between the balls location and the bricks areas. This may not be the best approach for detecting collisions but I believe it does a fair job.
Some background on how my logic worked when designing this method first. If the brick is a rectangle with the (x,y) coordinate marking the top left corner than the following points are also points which lie on the bricks perimeter:
Java Code:
  
That being said it may seem a little easier to work this method out. It is invaluable to visualize the brick and the ball for this method, and perhaps trying to solve it by hand (with drawings if necessary). Much of this method involves range checking. First you want to decide whether the ball is in the range (x, x+BRICK_WIDTH) if it is you know it has a similar x location compared to the ball. Now you want to see how close the ball is to the brick. This is done with the y location of the ball. If the y location of the ball is in range of (y+BRICK_HEIGHT) it has hit the brick from underneath, and the method changes the x direction of the ball, and returns true. If it is in range of the y position you know it hits the brick from the top, and can reflect the balls direction and return true. Reflecting the ball simply involves multiplying the current dX of the ball by -1. While this doesn’t necessary yield the greatest ball movement, it is the simplest approach. Now that we have determined whether the brick was hit on the top or bottom, we can determine if it hits the sides, and take the correct action. If the ball is in the range (x, x+BRICK_HEIGHT) we know it has the potential to hit the brick. This step inverses the previous step. Previously we checked the x range, then y, this time we start at the y range and move to the x. If it is in the y range, we determine where the x is. If the is in range of (x+BRICK_WIDTH) we can reflect the ball and return true. If it is in range of x, we return true and reflect again. I keep mentioning 'in range' to avoid confusion I would like to discuss briefly what I mean by this. I simply mean the x or y position of the ball is close to the x or y of what we are currently testing. Usually I allowed a bit of a buffer space to avoid problems in the animation by using a buffer greater than dX or dY. This method is a bit more complex, but it still isn't terribly hard to follow. Once again I must emphasize the importance of asking questions if you are unsure of something (or if I didn't come across too clear).

Now that we have our components are written it’s time to use them in what is the actual game. The canvas class, this class is a bit more complex if you are unfamiliar to drawing on a canvas, but hopefully I will make it easy for you to understand. Here is the canvas class:
Java Code:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

import breakout.Brick.Type;

public class Canvas extends JPanel implements ActionListener, MouseMotionListener, MouseListener, KeyListener {
	/**
	 * 
	 */
	private static final long serialVersionUID = -5699255769305413877L;
	public static final int HEIGHT = 600;
	public static final int WIDTH = 720;
	
	private int horizontalCount;
	private BufferedImage image;
	private Graphics2D bufferedGraphics;
	private Timer time;
	private static final Font endFont = new Font(Font.SANS_SERIF, Font.BOLD, 20);
	private static final Font scoreFont = new Font(Font.SANS_SERIF, Font.BOLD, 15);
	
	private Paddle player;
	private Ball ball;
	ArrayList<ArrayList<Brick> > bricks;
	
	/**
	 * Prepares the screen, centers the paddle and the ball. The ball
	 * will be located in the center of the paddle, and the paddle will
	 * be located on the center of the screen
	 * Sunde
	 * The bricks are displayed in columns across the screen with the 
	 * screen being split based on the width of an individual brick. 
	 * Each brick is stored in a temporary ArrayList, which is added
	 * to the classes ArrayList which contains all of the bricks.
	 */
	public Canvas(){
		super();
		setPreferredSize(new Dimension(WIDTH, HEIGHT));
		image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
		bufferedGraphics = image.createGraphics();
		time = new Timer(15, this);
		player = new Paddle((WIDTH/2)-(Paddle.PADDLE_WIDTH/2));
		ball = new Ball(((player.getX() + (Paddle.PADDLE_WIDTH / 2)) - (Ball.DIAMETER / 2)), 
				(Paddle.Y_POS - (Ball.DIAMETER + 10)), -5, -5);
		
		bricks = new ArrayList<ArrayList<Brick> >();
		horizontalCount = WIDTH / Brick.BRICK_WIDTH;
		for(int i = 0; i < 8; ++i){
			ArrayList<Brick> temp = new ArrayList<Brick>();
			Type rowColor = null;
			switch(i){
				case 0:
				case 2:
					rowColor = Type.LOW;
					break;
				case 1:
				case 3:
				case 5:
					rowColor = Type.MEDIUM;
					break;
				case 4:
				case 6:
					rowColor = Type.HIGH;
					break;
				case 7:
				default:
					rowColor = Type.ULTRA;
					break;
			}
			for(int j = 0; j < horizontalCount; ++j){
				Brick tempBrick = new Brick((j * Brick.BRICK_WIDTH), ((i+2) * Brick.BRICK_HEIGHT), rowColor);
				temp.add(tempBrick);
			}
			bricks.add(temp);
			addMouseMotionListener(this);
			addMouseListener(this);
			addKeyListener(this);
			requestFocus();
		}
	}
	
	@Override public void actionPerformed(ActionEvent e){
		checkCollisions();
		ball.move();
		for(int i = 0; i < bricks.size(); ++i){
			ArrayList<Brick> al = bricks.get(i);
			for(int j = 0; j < al.size(); ++j){
				Brick b = al.get(j);
				if(b.dead()){
					al.remove(b);
				}
			}
		}
		repaint();
	}

	/**
	 * Checks for any collisions, if the ball hits the upper wall, or the side
	 * walls it changes direction. If the ball goes below the paddle, the position
	 * of the ball gets reset and the player loses a life
	 */
	private void checkCollisions() {
		if(player.hitPaddle(ball)){
			ball.setDY(ball.getDY() * -1);
			return;
		}
		//first check if ball hit any walls
		if(ball.getX() >= (WIDTH - Ball.DIAMETER) || ball.getX() <= 0){
			ball.setDX(ball.getDX() * -1);
		}
		if(ball.getY() > (Paddle.Y_POS + Paddle.PADDLE_HEIGHT + 10)){
			resetBall();
		}
		if(ball.getY() <= 0){
			ball.setDY(ball.getDY() * -1);
		}
		
		//next handle collisions between bricks
		int brickRowsActive = 0;
		for(ArrayList<Brick> alb : bricks){
			if(alb.size() == horizontalCount){
				++brickRowsActive;
			}
		}
		
		for(int i = (brickRowsActive==0) ? 0 : (brickRowsActive - 1); i < bricks.size(); ++i){
			for(Brick b : bricks.get(i)){
				if(b.hitBy(ball)){
					player.setScore(player.getScore() + b.getBrickType().getPoints());
					b.decrementType();
				}
			}
		}
	}
	
	/**
	 * Sets the balls position to approximately the center of the screen, and
	 * deducts a point from the user. If necessary, ends the game
	 */
	private void resetBall() {
		if(gameOver()){
			time.stop();
			return;
		}
		ball.setX(WIDTH/2);
		ball.setY((HEIGHT/2) + 80);
		player.setLives(player.getLives() - 1);
		player.setScore(player.getScore() - 1000);
	}
	
	private boolean gameOver() {
		if(player.getLives() <= 1)
			return true;
		return false;
	}

	/**
	 *  Draws the screen for the game, first sets the screen up (clears it)
	 *  and then it begins by setting the entire screen to be white. Finally
	 *  it draws all of the bricks, the players paddle, and the ball on the 
	 *  screen
	 */
	@Override public void paintComponent(Graphics g){
		super.paintComponent(g);
		bufferedGraphics.clearRect(0, 0, WIDTH, HEIGHT);
		bufferedGraphics.setColor(Color.WHITE);
		bufferedGraphics.fillRect(0, 0, WIDTH, HEIGHT);
		player.drawPaddle(bufferedGraphics);
		ball.drawBall(bufferedGraphics);
		for(ArrayList<Brick> row : bricks){
			for(Brick b : row){
				b.drawBrick(bufferedGraphics);
			}
		}
		bufferedGraphics.setFont(scoreFont);
		bufferedGraphics.drawString("Score: " + player.getScore(), 10, 25);
		if(gameOver() &&
				ball.getY() >= HEIGHT){
			bufferedGraphics.setColor(Color.black);
			bufferedGraphics.setFont(endFont);
			bufferedGraphics.drawString("Game Over!  Score: " + player.getScore(), (WIDTH/2) - 85, (HEIGHT/2));
		}
		if(empty()){
			bufferedGraphics.setColor(Color.black);
			bufferedGraphics.setFont(endFont);
			bufferedGraphics.drawString("You won!  Score: " + player.getScore(), (WIDTH/2) - 85, (HEIGHT/2));
			time.stop();
		}
		g.drawImage(image, 0, 0, this);
		Toolkit.getDefaultToolkit().sync();
	}
	
	

	private boolean empty() {
		for(ArrayList<Brick> al : bricks){
			if(al.size() != 0){
				return false;
			}
		}
		return true;
	}

	@Override public void mouseMoved(MouseEvent e){
		player.setX(e.getX() - (Paddle.PADDLE_WIDTH / 2));
	}
	
	@Override public void mouseDragged(MouseEvent e){}
	
	@Override public void mouseClicked(MouseEvent e){
		if(time.isRunning()){
			return;
		}
		time.start();
	}
	
	public static void main(String[] args){
		JFrame frame = new JFrame();
		Canvas c = new Canvas();
		frame.add(c);
		frame.pack();
		frame.setResizable(false);
		frame.setVisible(true);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}

	@Override
	public void mousePressed(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}
}
This class is quite larger than the rest, more than three times larger in fact. It is also a bit more complex since it is the code that essentially allows the game to play. I plan to start from the beginning and explain how each piece of this class works.
The constants specify the size of the canvas, they are named HEIGHT and WIDTH, and I will let you figure out what each dictates .
The horizontalCount variable will be explained later, when it is necessary, for now just ignore it. The way I like to draw is via a BufferedImage and Graphics2D object (since I am far from an expert, this approach may not be the best. It is what I will use though). These two variables are set up in the constructor and work together well, you first clear the image of everything, then you draw what you want on the image, and finally you draw the image onto the JPanel. I have the class as a subclass of JPanel, which was my own naïve approach, as a challenge for you, I suggest removing this and getting it to work. Doing this allows the class to be more flexible—it will work on anything which can draw an object. The Timer object is what allows animation and it works quite simply. You supply the Swing Timer with a time period, and an action listener. The time period, x, is in milliseconds and it will call the actionListener assigned to it every x milliseconds. In something like this, every x milliseconds, the ball will make a move, as will everything these which can move. The paddle will be repositioned, etc. Since x is in milliseconds, it makes sense to figure out how many frames per second there are. Since there are 1000 ms in one second, you find FPS with 1000/x. My timer uses 15 for x, so it produces ~67 frames per second. You can play around with the value of x and see how the game changes. In general, assuming nothing else changes, higher values of x will make the game animate slower, and smaller values will make it animate faster.

The two Fonts are really unimportant at this point (and fairly unimportant later as well). They are predefined fonts to use for certain drawing scenarios—scoreFont will be used to draw the score, and endFont will be used when you win or lose the game. The final thing the class needs are the “players”—the paddle, bricks, and ball. The Bricks are stared in an ArrayList of ArrayLists. This lends naturally to rows and columns, and I chose ArrayList over arrays for the simplicity of removing ‘dead’ bricks.

The constructor of this class begins by initializing the important instance variables and sets the position of the ball, and paddle. The ball and paddle are located at the center on the x plane, and Y_POS on the y plane. The ball is slightly higher than the paddle. Now is where horizontalCount comes into play, it is used to create how many bricks will be in each row of bricks. It’s calculated as simply the WIDTH divided by the BRICK_WIDTH. It is helpful to ensure that WIDTH % BRICK_WIDTH == 0 (WIDTH is evenly divisible by BRICK_WIDTH) for the best view. The row of bricks are arbitrarily set at 8, and a double loop is set up. This nested loop fills the arrayList of ArrayLists with the proper items. It creates a temporary list in the first loop, and then fills this list in the second loop and adds it to the main list. The type of the brick is determines based on the row number via a switch statement, and each brick is positioned to the right BRICK_WIDTH units. Each new row of bricks is drawn lower BRICK_HEIGHT units. Finally some listeners are added to allow the mouse input to be useful.

Earlier when I spoke of the Timer I talked about an actionListener which is ‘linked’ to it, this action listener is really the animation of the game, it is quite important. It checks collisions to determine if the ball hit something—the sides, the top, a brick, or the paddle. After checking for collisions it’s safe to assume the ball will be moving in the correct direction, and the ball is moved once via the balls move() method. Since it is possible for bricks to be dead it is important to check for this. The final part checks each brick to determine whether dead bricks exist, and if so, it removes them from the list. The action listener does not actually do any drawing it simply sets up conditions which will alter the view when the frame is redrawn. The changes will happen every 15 seconds, allowing a call to repaint to show the changes it just made.

The checkCollisions method is what really allows the game to work properly. As it implies by the name, it checks to see if the ball hits anything. This method is called every 15 milliseconds thanks to the Swing Timer. When called this method first checks if the ball hits the paddle, and if it does, it changes the balls direction. The logic for checking for a paddle hit is in the paddle class and was explained earlier. If the ball does hit the paddle, the method returns to prevent strange behavior—like the ball bouncing off the paddle and a side wall at the same time. If the ball has not hit the paddle, it next checks whether any of the wall have been hit. If the ball hits the left or right wall, it is reflected in the x position (dX multiplied by -1). If it hits the top wall it reflects in the y direction (dY multiplied by -1), and it also checks if the ball has been missed by the paddle. If the ball is missed, the resetBall() method is called—this method will be explained shortly. The final step may seem a bit complex, but I assure you, it’s not. We don’t want to check for collision’s with every brick—while this is probably feasible, it’s a waste of resources, and it’s easy to avoid. We calculate how many bricks rows are currently active by declaring a local variable, and initializing it to 0. Then we begin looking through the bricks array list. For each array list in bricks, we test the size of the array list to the horizontalCount variable. Basically, if any list is at the max size, we skip past it, this way we don’t check for collisions on row 0, when row 8 hasn’t been broken through. Now that we attained how many intact rows there are, we begin another loop, if there are 0 rows intact, we test all the bricks available, otherwise we test the bricks from brickRowsActive-1 to the size of the bricks arrayList. We subtract one from brickRows active because it is very likely that row 7 will be full, and row 8 will be less than full. If we didn’t subtract 1, it would only check row 8, if for some reason the ball passed row 8 without hitting anything it would go through the remaining bricks. Try this and see for yourself. Surprisingly enough, the game won’t ever end because the initial size of the brickRowsActive variable will end up being bricks.size(), and no checking will be performed. Now that we figured that out, we will actually do our checking to see if bricks are hit. I use a for each loop here, for each brick in the ith arraylist, we will do collision detection on. The collision detection is all performed in the brick class, we simply call the method on each brick with the ball as the argument. When a brick is hit, two things must be done, first we add points to the player score—this is determined by the current brick type. Then we decrement the bricks type with the method decrementType in the brick class.

The resetBall method was called in the previous method (checkCollisions), and now it is explained. The method first checks if the game is over, and if it does, it stops the Timer, which stops all animation. Before stopping the timer it decrements the players lives, which will make them 0. The gameOver() method returns true if the player has 1 or less lives left. Next, the resetBall method positions the ball near the center of the screen, lowers the players lives by 1, and reduces there score by 1000.

Submit "Breakout Tutorial" to Facebook Submit "Breakout Tutorial" to Digg Submit "Breakout Tutorial" to del.icio.us Submit "Breakout Tutorial" to StumbleUpon Submit "Breakout Tutorial" to Google

Tags: None Add / Edit Tags
Categories
Uncategorized