I am trying to create the Breakout game with a ball, paddle, and bricks. Here is
ID: 3901869 • Letter: I
Question
I am trying to create the Breakout game with a ball, paddle, and bricks. Here is what I have so far.
// Using a Timeline for animation, we move a bouncing ball around the Pane
import javafx.application.Application;
import javafx.stage.*;
import javafx.event.*;
import javafx.animation.*;
import javafx.event.*;
import javafx.scene.*;
import javafx.scene.shape.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import java.util.*; // for Random
import javafx.util.*;
import javafx.scene.input.*; // added from last exercise to include KeyCodes
public class ch15_ex6 extends Application
{
private double bx, by, bdx, bdy; // x,y coordinate of ball and its velocity
private double px, pdx; // paddle's x coordinate and x velocity
private Circle c; // the ball
private Rectangle r;
private Pane pane; // the pane to draw on
public static void main(String[] args)
{
launch();
}
@Override
public void start(Stage primaryStage)
{
Random g = new Random();
bx = Math.abs(g.nextInt())%450 + 25; // start off at random x,y coordinate
by = Math.abs(g.nextInt())%450 + 25;
do { // start off with random velocity between [-3..3,-3..3]
bdx = Math.abs(g.nextInt())%7 - 3;
bdy = Math.abs(g.nextInt())%7 - 3;
}while(bdx==0||bdy==0); // but neither should be 0
px = 220; // start the paddle in the middle with no velocity
pdx = 0;
c = new Circle(bx,by,20); // create the ball
r = new Rectangle(px,450,60,8);
pane = new Pane();
pane.getChildren().add(c); // add initial ball to pane
pane.getChildren().add(r); // add paddle to pane
Scene scene = new Scene(pane, 500,500);
Timeline timer = new Timeline(new KeyFrame(Duration.millis(20),e -> {
pane.getChildren().remove(c); // set up our Timeline, for
pane.getChildren().remove(r); // every action, remove current ball and paddle
if(bx>450) bdx*=-1;
else if(bx<50) bdx*=-1; // and adjust bdx and/or bdy if hitting a "wall"
if(by>450) bdy*=-1;
else if(by<50) bdy*=-1;
bx+=bdx; // move position of ball
by+=bdy;
c = new Circle(bx,by,20); // redraw the ball
pane.getChildren().add(c);
if(px<50) { px = 50; pdx=0;} // if paddle hits a wall, stop it
else if(px>390) {px = 390; pdx = 0;} // note: reset px in case its < 50 or > 390
px+=pdx; // using 390 because px is the left end of the paddle, not the middle
r = new Rectangle(px,450,60,8);
pane.getChildren().add(r);
}));
timer.setCycleCount(Timeline.INDEFINITE);
timer.play(); // Start animation
scene.setOnKeyPressed(e -> { // register the event handler to the scene
if(e.getCode()==KeyCode.LEFT&&pdx>-3) pdx--; // change paddle's velocity based on
else if(e.getCode()==KeyCode.RIGHT&&pdx<3) pdx++; // left/right/down arrow
else if(e.getCode()==KeyCode.DOWN) pdx=0; // unless already at max velocity
});
primaryStage.setTitle("bouncing ball");
primaryStage.setScene(scene);
primaryStage.show();
}
}
Here is what the directions say:
There should be a separate Brick class. The Brick class contains it’s x,y coordinate, Color and value and whether it is currently visible or not. Aside from the constructor(s) (see the program instructions), have getters for all of the instance data and a collides method. You would use two nested for loops whenever the timer advances the ball doing
for(int i=0;i<cols;i++)
for(int j=0;j<rows;j++)
score += if(bricks[j][i].collides(pane, bx,by)) {…}
This will cause the current Brick to see if the ball hits it and if so, set that Brick to not be visible, remove it from the pane, and return the Brick’s worth to be added to the score.
For this assignment, you will create the game Breakout. If you do not know Breakout, it is a game with a bouncing ball, paddle and a series of bricks. The object is to hit the ball with the paddle so that it bounces to the other end of the game field and hits one or more bricks. As each brick is hit it disappears and the user scores points. If the user can clear the entire field of breaks, then the game resets with a new field of bricks. You may have already implemented a portion of this if you have done chapter 15 exercises 5 & 6. If you haven't look at my solution to see my code. You should start with your own code though as you will want to implement this in your own way. How the game works: 1. Have bx, by, bdx and bdy variables to represent the ball's location and velocity. The ball is a Circle 2. Have px and pdx to represent the paddle's upper-left corner and its motion in the x direction. The paddle does not move in the y direction. The paddle is a Rectangle. inner class or a class in a separate file, your choice) event handlers 3. Have any array of Bricks. Bricks are defined below as a separate class (either a nested 4. Declare the following as instance data as they will be shared among various methods and Circle ball - the ball (needed to add to and remove from the Pane as it moves) a. b. Rectangle paddle - the paddle (add and remove from Pane as it moves) int score, lives - player's current score and lives remaining c. d. Text description - output of the current score and number of lives remaining bx, by, bdx, bdy, px, pdx as noted above (used in several locations) e. f. the array of Bricks (this is a 2-D array) the Pane and the Timeline object g. h. a Random generator Your main method will call launc Your start method will instantiate or initialize all of your instance data 6. a. for the ball, set bx to the middle horizontally and by to a value beneath the Bricks but well above the paddle, give px a value in the middle horizontally, pdx should start at 0, bdx and bdy should be given random values as long as bdy is positive (so that initially, the ball is moving downward), limit bdx, bdy, pdx to a reasonable range (say -3 to +3) b. create the Text, Circle and Rectangle objects and add them to the Pane; also draw four Lines to have borders around the playing field instantiate all of the Brick objects and for each Brick object, pass it the Pane object; Brick will contact a draw method that will draw a Brick onto the Pane (described below) c. d. create a Timeline object with its own event handler (described in 7) and attach to the Scene a Key EventHandler (described in 8), use a reasonable duration (mine was 15, if you have a larger number, you will want to allow larger ranges for bdx/bdy and pdx)Explanation / Answer
Ball.java
import javafx.scene.shape.Circle;
import javafx.scene.layout.Pane;
import javafx.geometry.Bounds;
import java.util.Random;
import java.util.List;
import java.util.ArrayList;
/**
* This class handles ball animation and calls the appropriate collision check methods.
*/
public class Ball {
private Circle ball;
private double dx;
private double dy;
private Bricks bricks;
private Paddle paddle;
private List<LevelListener> lossListeners = new ArrayList<LevelListener>();
/**
* Creates a new ball handler object.
*
* @param ball The Circle object that represents the ball.
* @param bricks The Bricks handler.
* @param paddle The Paddle handler.
* @param speed The initial speed of the ball, in pixels per update.
*/
public Ball(Circle ball, Bricks bricks, Paddle paddle, double speed) {
this.ball = ball;
this.bricks = bricks;
this.paddle = paddle;
setStartingSpeed(speed);
}
/**
* A convenience constructor that has a pre-set speed for new games.
*
* @param ball The Circle object that represents the ball.
* @param bricks The Bricks handler.
* @param paddle The Paddle handler.
*/
public Ball(Circle ball, Bricks bricks, Paddle paddle) {
this(ball, bricks, paddle, 4);
}
/**
* Sets the starting speed of the ball.
*
* @param speed The initial speed of the ball, in pixels per update.
*/
public void setStartingSpeed(double speed) {
// Randomize initial starting angle
this.dx = (new Random()).nextBoolean() ? speed : -speed;
this.dy = -speed;
}
/**
* Adds a listener object that is called by animate() when the player loses.
*
* @param newListener A LevelListener object to attach.
*/
public void addLossListener(LevelListener newListener) {
this.lossListeners.add(newListener);
}
/**
* Animates the ball and handles collision detection.
*/
public void animate() {
// Move the ball
this.ball.setTranslateX(this.ball.getTranslateX() + dx);
this.ball.setTranslateY(this.ball.getTranslateY() + dy);
Bounds bounds = this.ball.getBoundsInParent();
Pane canvas = (Pane)this.ball.getParent();
final boolean atTopBorder = bounds.getMinY() <= 0;
final boolean atRightBorder = bounds.getMaxX() >= canvas.getWidth();
final boolean atBottomBorder = bounds.getMaxY() >= canvas.getHeight();
final boolean atLeftBorder = bounds.getMinX() <= 0;
final int atBrick = this.bricks.checkCollision(this.ball);
final int atPaddle = this.paddle.checkCollision(this.ball);
// Calculate angle of reflection (bounce the ball off things)
if(atLeftBorder || atRightBorder || atBrick == -1) dx *= -1;
if(atTopBorder || atBrick == 1 || atPaddle == 1) dy *= -1;
// If the ball hits the bottom of the screen, the player loses a life
// Notify any attached loss listeners
if(atBottomBorder) {
for(LevelListener ls : this.lossListeners) {
ls.handleLevelingEvent();
}
}
}
/**
* Convenience method to access the internal node's getTranslateX() method.
*
* @return The Circle's translateX property.
*/
public double getTranslateX() {
return this.ball.getTranslateX();
}
/**
* Convenience method to access the internal node's getTranslateY() method.
*
* @return The Circle's translateY property.
*/
public double getTranslateY() {
return this.ball.getTranslateY();
}
/**
* Convenience method to access the internal node's setTranslateX() method.
*
* @param x The x-axis value to set.
*/
public void setTranslateX(double x) {
this.ball.setTranslateX(x);
}
/**
* Convenience method to access the internal node's setTranslateY() method.
*
* @param y The y-axis value to set.
*/
public void setTranslateY(double y) {
this.ball.setTranslateY(y);
}
/**
* Returns the internal node held by this handler.
*
* @return The held Circle object.
*/
public Circle getNode() {
return this.ball;
}
}
Bricks.java
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import javafx.geometry.Bounds;
import javafx.scene.Node;
import javafx.scene.layout.GridPane;
import javafx.scene.shape.Circle;
import javafx.scene.layout.Region;
/**
* This class handles brick collision with a given ball, as well as damage values for the bricks.
*/
public class Bricks {
private GridPane bricks;
private List<Node> brickList;
private int[][] damage;
private int bricksCleared;
private final List<String> damageStyles = Arrays.asList("damage-1", "damage-2", "damage-3");
private List<LevelListener> winListeners = new ArrayList<LevelListener>();
/**
* Creates a Bricks handler object.
*
* @param bricks The GridPane object that holds the aforementioned bricks.
*/
public Bricks(GridPane bricks) {
this.bricks = bricks;
this.brickList = bricks.getChildren();
this.damage = new int[4][6];
}
/**
* Adds a listener object that is called by increaseDamange() when the player clears all bricks.
*
* @param newListener A LevelListener object to attach.
*/
public void addWinListener(LevelListener newListener) {
this.winListeners.add(newListener);
}
/**
* Checks for a collision between the given ball and any brick in our GridPane.
*
* @param ball The ball to check for collisions against.
* @return 1 for a horizontal hit, -1 for a vertical hit, and 0 for no hit.
*/
public int checkCollision(Circle ball) {
Bounds ballBounds = ball.getBoundsInParent();
final double ballMinX = ballBounds.getMinX();
final double ballMinY = ballBounds.getMinY();
final double ballMaxX = ballBounds.getMaxX();
final double ballMaxY = ballBounds.getMaxY();
final boolean atBricksTop = ballMinY >= this.bricks.getLayoutY() - ball.getRadius() * 2;
final boolean atBricksBottom = ballMaxY <= (this.bricks.getLayoutY() + this.bricks.getHeight()) + ball.getRadius() * 2;
// Only check for collisions if the ball is near the brick field
if (atBricksTop && atBricksBottom) {
// Check in reverse from bottom to top for speed
for(int i = this.brickList.size() - 1; i >= 0; i--) {
Region brick = (Region)this.brickList.get(i);
// Skip already broken bricks
if(!brick.isVisible()) {
continue;
}
// Precalculate bounds
Bounds brickBounds = brick.getBoundsInParent();
final double brickMinX = this.bricks.getLayoutX() + brickBounds.getMinX();
final double brickMinY = this.bricks.getLayoutY() + brickBounds.getMinY();
final double brickMaxX = brickMinX + brickBounds.getWidth();
final double brickMaxY = brickMinY + brickBounds.getHeight();
final boolean insideX = ballMaxX >= brickMinX && ballMinX <= brickMaxX;
final boolean insideY = ballMaxY >= brickMinY && ballMinY <= brickMaxY;
// Verify that the ball is touching/colliding with the current brick
if(insideX && insideY) {
final boolean atTop = ballMinY < brickMinY;
final boolean atBottom = ballMaxY > brickMaxY;
final boolean atLeft = ballMinX < brickMinX;
final boolean atRight = ballMaxX > brickMaxX;
if(atTop || atBottom) {
increaseDamage(brick);
return 1;
}
if(atLeft || atRight) {
increaseDamage(brick);
return -1;
}
}
}
}
return 0;
}
/**
* Increments the damage level of the given brick.
*
* @param brick The Region object representing the brick.
*/
public void increaseDamage(Region brick) {
List<String> styles = brick.getStyleClass();
// Get row & col from style classes
int row = Integer.valueOf(styles.get(0).substring(4));
int col = Integer.valueOf(styles.get(1).substring(4));
this.damage[row][col]++;
// Damage of 3 indicates a broken brick
if(this.damage[row][col] == 3) {
brick.setVisible(false);
this.bricksCleared++;
if(isCleared()) {
// Notify any attached listeners of a win
for(LevelListener ls : winListeners) {
ls.handleLevelingEvent();
}
}
}
// Remove all damage styles and add the new one
styles.removeAll(this.damageStyles);
styles.add("damage-" + this.damage[row][col]);
// Debug
System.out.println(Arrays.toString(styles.toArray()));
}
/**
* Checks to see if all bricks have been broken.
*
* @return True if the brick field is clear, false if it isn't.
*/
public boolean isCleared() {
System.out.println(bricksCleared + " bricks cleared");
return this.bricksCleared == this.brickList.size();
}
/**
* Resets the brick field and removes all damage.
*/
public void reset() {
for(Node brick : this.brickList) {
brick.setVisible(true);
brick.getStyleClass().removeAll(this.damageStyles);
}
this.damage = new int[4][6];
this.bricksCleared = 0;
}
}
Paddle.java
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Circle;
import javafx.scene.layout.Pane;
import javafx.geometry.Bounds;
/**
* This class handles paddle animation and provides a method to check collision against a given ball.
*/
public class Paddle {
private Rectangle paddle;
/**
* Creates a new Paddle object.
*
* @param paddle The Rectangle object representing the paddle.
*/
public Paddle(Rectangle paddle) {
this.paddle = paddle;
}
/**
* Animates the paddle at the given speed.
* The paddle will not move outside of its parent's bounds.
*
* @param dx The speed in pixels per update at which to move the paddle horizontally.
* @return True if the paddle was animated, false if it wasn't.
*/
public boolean animate(double dx) {
Bounds bounds = this.paddle.getBoundsInParent();
final boolean atLeftBorder = bounds.getMinX() + dx <= 0;
final boolean atRightBorder = bounds.getMaxX() + dx >= ((Pane)this.paddle.getParent()).getWidth();
// Don't allow paddle to be moved outside of the game canvas
// Return true if the paddle was moved
if (!atLeftBorder && !atRightBorder) {
this.paddle.setTranslateX(this.paddle.getTranslateX() + dx);
return true;
}
else {
return false;
}
}
/**
* Checks if there is a collision between the paddle and the given ball.
*
* @param ball The Circle object representing the ball.
* @return 1 if there is a collision, 0 if there isn't.
*/
public int checkCollision(Circle ball) {
Bounds ballBounds = ball.getBoundsInParent();
Bounds paddleBounds = this.paddle.getBoundsInParent();
final double insideY = ballBounds.getMaxY() - paddleBounds.getMinY();
if(ballBounds.intersects(paddleBounds)) {
// Quick & dirty hack to fix deep collisions
if(insideY > 3) {
ball.setTranslateY(-(insideY - 3));
}
return 1;
}
return 0;
}
/**
* Convenience method to access the internal node's getTranslateX() method.
*
* @return The Rectangle's translateX property.
*/
public double getTranslateX() {
return this.paddle.getTranslateX();
}
/**
* Convenience method to access the internal node's setTranslateX() method.
*
* @param x The x-axis value to set.
*/
public void setTranslateX(double x) {
this.paddle.setTranslateX(x);
}
/**
* Returns the internal node held by this handler.
*
* @return The held Rectangle object.
*/
public Rectangle getNode() {
return this.paddle;
}
}
Related Questions
drjack9650@gmail.com
Navigate
Integrity-first tutoring: explanations and feedback only — we do not complete graded work. Learn more.