I am studying java with the book "The Art and Science of Java: An Introduction to Computer Science". One of the practice programs is to create a simple clone of the Breakout game.
I am currently able to load the game, but am having issues with the ball physics. I'm using the simplest physics possible, and can't see why it isn't working.
When the ball hits a wall, it bounces normally, but when it hits the paddle or a brick, it doesn't. I'm using the same code in the collision detection to change the direction that I used with the walls. The collisions are detected, I added a println and watched the console as it went through the collision events, but the direction change is not happening.
package chapter10;
/*
* This program creates a clone of the classic Breakout Game,
* where a player bounces a ball with a paddle to "break" bricks
* along the top of the screen.
*
* Controls: Left: left button, Right: right button
*/
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.RepaintManager;
import javax.swing.Timer;
import acm.program.*;
import acm.graphics.*;
public class BreakoutClone extends GraphicsProgram {
/* components */
private GRect paddle;
private GRect brick;
private GOval ball;
/* static variables */
private static final double PADDLE_HEIGHT = 5;
private static final double BALL_SPEED = 2;
private static final double PADDLE_SPEED = 2;
private static final double ROWS_BRICKS = 6;
private static final double COLUMNS_BRICKS = 10;
private static final double TOP_GAP = 50;
/* variables */
private int numTurns = 3;
private double paddleWidth = 50;
private int dx = 2;
private int dy = 2;
public void init() {
setSize(700, 600);
paddle = new GRect(0, getHeight() - 30, paddleWidth, PADDLE_HEIGHT);
paddle.setFilled(true);
add(paddle);
addBricks();
ball = new GOval(getWidth() / 2, 175, 5, 5);
ball.setFilled(true);
add(ball);
}
public void run() {
animateBall();
// TODO animate paddle
}
public void addBricks() {
double gap = 20;
double brickWidth = getWidth() / COLUMNS_BRICKS;
for (int r = 0; r < ROWS_BRICKS; r++) {
for (int b = 0; b < COLUMNS_BRICKS; b++) {
brick = new GRect(b * brickWidth, gap * r + TOP_GAP,
brickWidth, 10);
brick.setFilled(true);
add(brick);
}
}
}
public void endGame() {
// TODO write end game method
}
public void animateBall() {
while (numTurns > 0) {
ball.move(dx, dy);
pause(15);
/* Look for Object Collision */
GObject topRightObject = getElementAt(ball.getX() + 5, ball.getY());
GObject topLeftObject = getElementAt(ball.getX(), ball.getY());
GObject botRightObject = getElementAt(ball.getX() + 5,
ball.getY() + 5);
GObject botLeftObject = getElementAt(ball.getX(), ball.getY() + 5);
/* Bounce off walls */
if ((ball.getX() >= getWidth() - 5) || ball.getX() <= 0) {
dx = -dx;
}
if (ball.getY() <= 0) {
dy = -dy;
}
if ((ball.getY() >= getHeight() - 5)) {
dy = -dy;
// numTurns--;
// if (numTurns == 0) {
// endGame();
// } else {
// run();
// }
}
/* Bounce off objects, remove bricks */
if (topRightObject != null) {
dy = -dy;
hasCollided(topRightObject);
}
if (topLeftObject != null) {
dy = -dy;
hasCollided(topLeftObject);
}
if (botRightObject != null) {
dy = -dy;
hasCollided(botRightObject);
}
if (botLeftObject != null) {
dy = -dy;
hasCollided(botLeftObject);
}
}
}
private void hasCollided(GObject obj) {
if (obj.equals(paddle)) {
System.out.println("detecting paddle");
} else {
System.out.println("detecting brick");
remove(obj);
}
}
}
Add an if(dy<0) around the part that reverses direction of the ball when it hits the paddle (assuming a negative dy means the ball is heading towards the paddle). This removes the possibility that your direction is being changed, but more than once per collision
EDIT: updated code and question
I added main() method as stated in aswers but I still can't export it.
I am running my program as Java Applet, and apparently I need to use Java Application to run it standalone, but when I change run configuration to Application i get these errors:
Exception in thread "main" java.lang.NullPointerException
at acm.graphics.GImage.determineSize(GImage.java:564)
at acm.graphics.GImage.setImage(GImage.java:173)
at acm.graphics.GImage.<init>(GImage.java:115)
at acm.graphics.GImage.<init>(GImage.java:54)
at Pong.createTexture(Pong.java:160)
at Pong.run(Pong.java:81)
at Pong.main(Pong.java:55)
I need to export my project from Eclipse as a standalone runnable JAR, but when i go to export -> java -> JAR file i dont see any classes to select and im getting stuck at this (screen) window. I only have one class in my project.
This is not relevant anymore but I'll leave it here to keep edit history
import java.awt.Color;
import java.awt.Image;
import java.awt.event.MouseEvent;
import java.util.Random;
import acm.graphics.GImage;
import acm.graphics.GLabel;
import acm.graphics.GObject;
import acm.graphics.GOval;
import acm.graphics.GRect;
import acm.program.GraphicsProgram;
/* TO DO LIST
* ------------------
* Corner Bounce
*
*
*
*/
#SuppressWarnings("serial")
public class Pong extends GraphicsProgram {
public double mouseY;
private static final double PAUSE = 1000 / 96.0;
private Random rand = new Random();
private boolean AI_GODMODE = false;
// ball
public double startX;
public double startY;
private static final double BALL_SIZE = 20;
private static final double SPEED = 5;
private double ballHorizontalSpeed = SPEED * 1.5;
private double ballVerticalSpeed = SPEED;
// paddle
private static int HEIGHT = 150;
private static int WIDTH = 15;
private static int COUNTER = 0;
private static int AI_SPEED = 10; // AI difficulty 1-20
// label
public int AI_SCORE = 0;
public int PLAYER_SCORE = 0;
public int TOTAL_GAMES = 0;
private float TRANSPARENCY = 0.0f;
// counters
private static final int PADDING = 10;
private static final int MODIFIER = 3;
public static void main(String[] args) {
Pong p = new Pong();
p.run();
}
public void run() {
addMouseListeners();
// counters setup
GLabel counter = new GLabel(String.valueOf(COUNTER));
GLabel aiScore = new GLabel(String.valueOf(AI_SCORE));
GLabel average = new GLabel(String.valueOf("Avg: 0"));
GLabel playerScore = new GLabel(String.valueOf(COUNTER));
Color labelC = new Color(0, 0.0f, 0.0f, TRANSPARENCY);
Color scoreC = new Color(0, 0.0f, 0.0f, 0.1f);
counter.setFont("Impact-600");
aiScore.setFont("Impact-100");
average.setFont("Impact-50");
playerScore.setFont("Impact-100");
counter.setColor(labelC);
aiScore.setColor(scoreC);
playerScore.setColor(scoreC);
average.setColor(scoreC);
counter.setLocation(getWidth() / 2 - counter.getWidth() / 2,
getHeight() / 2 + counter.getHeight() / 3.2);
counter.sendToFront();
// make objects
GImage paddleLeftTexture = createTexture("texture.png", WIDTH + 1,
HEIGHT + 1);
GImage paddleRightTexture = createTexture("texture2.png", WIDTH + 1,
HEIGHT + 1);
GImage ballTexture = createTexture("ballTexture.png", (int) BALL_SIZE,
(int) BALL_SIZE);
GImage greenFlash = createTexture("greenFlash.png", 100, 300);
GImage blueFlash = createTexture("blueFlash.png", 100, 300);
GOval ball = makeBall();
GRect paddleLeft = makePaddle();
GRect paddleRight = makePaddle();
greenFlash.setLocation(-200, 0);
blueFlash.setLocation(-200, 0);
// generate GUI
drawGraphics(ball, paddleLeftTexture, paddleRightTexture, ballTexture,
greenFlash, blueFlash, counter, paddleLeft, paddleRight,
aiScore, playerScore, average);
// game start
bounce(labelC, aiScore, playerScore, counter, ball, paddleLeft,
paddleRight, paddleLeftTexture, paddleRightTexture,
ballTexture, greenFlash, blueFlash, average);
}
public void bounce(Color labelC, GLabel aiScore, GLabel playerScore,
GLabel counter, GOval ball, GRect paddleLeft, GRect paddleRight,
GImage paddleLeftTexture, GImage paddleRightTexture,
GImage ballTexture, GImage greenFlash, GImage blueFlash,
GLabel average) {
preGameSetup(ball, paddleRight, paddleRightTexture, counter);
updateAiScore(aiScore);
updatePlayerScore(playerScore);
updateAverage(average);
while (true) {
moveBall(ballHorizontalSpeed, ballVerticalSpeed, ball, ballTexture);
movePlayerPaddle(paddleLeft, paddleLeftTexture);
moveAiPaddle(ball, paddleRight, paddleRightTexture);
detectHit(ball, paddleRight, paddleLeft, counter, labelC);
if (TRANSPARENCY >= 0.0f) {
TRANSPARENCY -= TRANSPARENCY / 100f;
}
labelC = new Color(0, 0.0f, 0.0f, TRANSPARENCY);
counter.setColor(labelC);
if (detectBallOffScreen(ball)) {
ballOffScreen(ball, ballTexture, aiScore, playerScore,
greenFlash, blueFlash, average);
COUNTER = 0;
bounce(labelC, aiScore, playerScore, counter, ball, paddleLeft,
paddleRight, paddleLeftTexture, paddleRightTexture,
ballTexture, greenFlash, blueFlash, average);
}
pause(PAUSE);
}
}
public static GRect makePaddle() {
GRect result = new GRect(0, 0, WIDTH, HEIGHT);
result.setFilled(true);
result.setColor(Color.BLACK);
return result;
}
public static GOval makeBall() {
GOval result = new GOval(150, 100, BALL_SIZE, BALL_SIZE);
result.setFilled(true);
result.setColor(Color.WHITE);
return result;
}
private GImage createTexture(String importedImage, int width, int height) {
Image importResult = getImage(getCodeBase(), importedImage);
GImage textureResult = new GImage(importResult);
textureResult.setSize(width, height);
return textureResult;
}
public void mouseMoved(MouseEvent e) {
mouseY = e.getY();
}
private boolean ballHitBottom(GOval ball) {
double bottomY = ball.getY() + ball.getHeight();
return bottomY >= getHeight();
}
private boolean ballHitTop(GOval ball) {
double topY = ball.getY();
return topY <= 0;
}
private boolean ballHitPaddleRight(GOval ball, GRect paddle) {
double rightX = ball.getX() + ball.getWidth();
double rightY = ball.getY() + ball.getHeight() / 2;
double paddlePosX = paddle.getX();
double paddlePosY = paddle.getY();
if (rightX >= paddlePosX && rightY >= paddlePosY
&& rightY <= paddlePosY + paddle.getHeight())
return true;
else
return false;
}
private boolean detectBallOffScreen(GOval ball) {
if (ball.getX() < 2 * WIDTH - BALL_SIZE
|| ball.getX() > getWidth() - 2 * WIDTH)
return true;
else
return false;
}
private boolean ballHitPaddleLeft(GOval ball, GRect paddle) {
double leftX = ball.getX();
double leftY = ball.getY();
double paddlePosX = paddle.getX() + WIDTH;
double paddlePosY = paddle.getY();
if (leftX <= paddlePosX && leftY >= paddlePosY
&& leftY <= paddlePosY + paddle.getHeight())
return true;
else
return false;
}
/*
* private boolean ballHitPaddleBorder(GOval ball, GRect paddle) { ; if
* (ball.getX() > paddle.getX() - BALL_SIZE && ball.getX() < paddle.getX() +
* WIDTH && ball.getY() > paddle.getY() && ball.getY() < paddle.getY() +
* ballVerticalSpeed) return true; else if (ball.getX() > paddle.getX() -
* BALL_SIZE && ball.getX() < paddle.getX() + WIDTH && ball.getY() >
* paddle.getY() + HEIGHT && ball.getY() < paddle.getY() + HEIGHT -
* ballVerticalSpeed) return true; else return false; }
*/
private void preGameSetup(GObject ball, GObject paddleRight,
GObject paddleRightTexture, GLabel counter) {
startX = rand.nextInt((int) (getWidth() * 0.8))
+ (int) (0.1 * getWidth()); // zapobiega pojawieniu się piłki po
// lewej stronie lewej paletki
startY = rand.nextInt(getHeight());
ball.setLocation(startX, startY);
paddleRightTexture.setLocation(getWidth() - MODIFIER * WIDTH, startY
- HEIGHT / 2);
paddleRight.setLocation(getWidth() - MODIFIER * WIDTH, startY - HEIGHT
/ 2);
paddleRightTexture.sendToFront();
counter.setLabel(String.valueOf(COUNTER));
counter.setLocation(getWidth() / 2 - counter.getWidth() / 2,
getHeight() / 2 + counter.getHeight() / 3.2);
ballHorizontalSpeed = SPEED * 1.5;
ballVerticalSpeed = SPEED;
}
private void updateAiScore(GLabel aiScore) {
aiScore.setLabel(String.valueOf(AI_SCORE));
aiScore.setLocation(getWidth() - aiScore.getWidth() - MODIFIER * WIDTH
- PADDING, getHeight() - PADDING);
}
private void updatePlayerScore(GLabel playerScore) {
playerScore.setLabel(String.valueOf(PLAYER_SCORE));
playerScore.setLocation(MODIFIER * WIDTH + PADDING, getHeight()
- PADDING);
}
private void updateScore(GLabel counter, Color labelC) {
counter.setLabel(String.valueOf(COUNTER));
counter.setLocation(getWidth() / 2 - counter.getWidth() / 2,
getHeight() / 2 + counter.getHeight() / 3.2);
TRANSPARENCY = 0.1f;
labelC = new Color(0, 0.0f, 0.0f, TRANSPARENCY);
counter.setColor(labelC);
}
private void updateAverage(GLabel average) {
if (TOTAL_GAMES == 0) {
average.setLabel("Round: 1 Avg: 0");
} else {
average.setLabel("Round: " + String.valueOf(TOTAL_GAMES + 1) + " Avg: "
+ String.valueOf((int) ((AI_SCORE + PLAYER_SCORE) / TOTAL_GAMES)));}
average.setLocation(getWidth() / 2 - average.getWidth() / 2,
getHeight() - PADDING);
}
private void drawGraphics(GObject ball, GObject paddleLeftTexture,
GObject paddleRightTexture, GObject ballTexture,
GObject greenFlash, GObject blueFlash, GObject counter,
GObject paddleLeft, GObject paddleRight, GObject aiScore,
GObject playerScore, GLabel average) {
add(ball);
add(paddleLeftTexture);
add(paddleRightTexture);
add(ballTexture);
add(greenFlash);
add(blueFlash);
add(counter);
add(paddleLeft);
add(paddleRight);
add(aiScore);
add(playerScore);
add(average);
}
private void detectHit(GOval ball, GRect paddleRight, GRect paddleLeft,
GLabel counter, Color labelC) {
if (ballHitBottom(ball) && ballVerticalSpeed >= 0) {
ballVerticalSpeed *= -1;
}
if (ballHitTop(ball) && ballVerticalSpeed <= 0) {
ballVerticalSpeed *= -1;
}
if (ballHitPaddleRight(ball, paddleRight)) {
ballHorizontalSpeed *= -1;
}
if (ballHitPaddleLeft(ball, paddleLeft)) {
ballHorizontalSpeed *= -1;
COUNTER++;
updateScore(counter, labelC);
boolean bool = rand.nextBoolean();
if (bool)
if (ballHorizontalSpeed > 0)
ballHorizontalSpeed += 1;
else
ballHorizontalSpeed -= 1;
else if (ballVerticalSpeed > 0)
ballVerticalSpeed += 0.5;
else
ballVerticalSpeed -= 0.5;
}
/*
* if(ballHitPaddleBorder(ball, paddleLeft)){ ballVerticalSpeed *= -1; }
*
* if(ballHitPaddleBorder(ball, paddleRight)){ ballVerticalSpeed *= -1;
* }
*/
}
private void ballOffScreen(GOval ball, GObject ballTexture, GLabel aiScore,
GLabel playerScore, GObject greenFlash, GObject blueFlash,
GLabel average) {
if (ball.getX() < 2 * WIDTH - BALL_SIZE) { // left
double pos = ball.getY() - greenFlash.getHeight() / 2;
ballTexture.move(-ballTexture.getWidth() * 2, 0);
AI_SCORE += COUNTER;
TOTAL_GAMES++;
updateAiScore(aiScore);
updateAverage(average);
for (int i = 20; i < 100; i += 5) {
greenFlash.setLocation(-i, pos);
pause(25);
}
} else { // right
double pos = ball.getY() - blueFlash.getHeight() / 2;
ballTexture.move(ballTexture.getWidth() * 2, 0);
PLAYER_SCORE += COUNTER;
TOTAL_GAMES++;
updatePlayerScore(playerScore);
updateAverage(average);
for (int i = 20; i < 100; i += 5) {
blueFlash.setLocation(getWidth() - blueFlash.getWidth() + i,
pos);
pause(25);
}
}
}
private void moveBall(double ballHorizontalSpeed, double ballVerticalSpeed,
GObject ball, GObject ballTexture) {
ball.move(ballHorizontalSpeed, ballVerticalSpeed);
ballTexture.setLocation(ball.getX(), ball.getY());
ballTexture.sendToFront();
}
private void movePlayerPaddle(GObject paddleLeft, GObject paddleLeftTexture) {
if (mouseY < getHeight() - HEIGHT) { // Player
paddleLeft.setLocation(2 * WIDTH, mouseY);
paddleLeftTexture.setLocation(2 * WIDTH, mouseY);
paddleLeftTexture.sendToFront();
} else {
paddleLeft.setLocation(2 * WIDTH, getHeight() - HEIGHT);
paddleLeftTexture.setLocation(2 * WIDTH, getHeight() - HEIGHT);
paddleLeftTexture.sendToFront();
}
}
private void moveAiPaddle(GOval ball, GRect paddleRight,
GImage paddleRightTexture) {
if (AI_GODMODE == true) { // modeSelector
if (ball.getY() < getHeight() - HEIGHT / 2
&& ball.getY() > HEIGHT / 2) {
paddleRight.setLocation(getWidth() - MODIFIER * WIDTH,
ball.getY() - HEIGHT / 2);
paddleRightTexture.setLocation(getWidth() - MODIFIER * WIDTH,
ball.getY() - HEIGHT / 2);
paddleRightTexture.sendToFront();
} else if (ball.getY() <= HEIGHT / 2) {
paddleRight.setLocation(getWidth() - MODIFIER * WIDTH, 0);
paddleRightTexture.setLocation(getWidth() - MODIFIER * WIDTH,
-0);
paddleRightTexture.sendToFront();
} else {
paddleRight.setLocation(getWidth() - MODIFIER * WIDTH,
getHeight() - HEIGHT);
paddleRightTexture.setLocation(getWidth() - MODIFIER * WIDTH,
getHeight() - HEIGHT);
paddleRightTexture.sendToFront();
}
} else { // end godMode if
double targetY = ball.getY() + BALL_SIZE / 2;
if (targetY < getHeight() - HEIGHT / 2 && targetY > HEIGHT / 2) {
if (targetY < paddleRight.getY() + HEIGHT / 2) {
paddleRight.move(0, -AI_SPEED);
paddleRightTexture.move(0, -AI_SPEED);
} else if (targetY > paddleRight.getY() + HEIGHT / 2) {
paddleRight.move(0, AI_SPEED);
paddleRightTexture.move(0, AI_SPEED);
}
} // end normalMode if
} // end modeSelector if
} // end moveAiPaddle void
} // end class
Judging from the linked image, your class Pong does not have a main method. It simply cant be exported as a runnable jar file, because you could never run it. Add a main method, or export to a standard java jar file (File -> Export -> Java -> JAR file). The jar file it produces using the latter method will NOT be runnable if there is no main method, period. You have to have a main method in order to run this code stand alone, because that is the entry point for the application.
Per your comment, You will need to create an instance of the Pong class inside of the main method and invoke its run() method:
public static void main(String[] args) {
Pong p = new Pong();
p.run();
}
If the run method of the Pong class is static, you wont need an instance, and you could do this:
public static void main(String[] args) {
Pong.run();
}
You should be exporting it as a "Runnable JAR file" instead of a "JAR file". Once you choose this you should be able to use a drop down menu called "Launch configuration:", and then you can choose your export destination.
I am using Eclipse Kepler. It may not be the same for different versions.
Your project should contain class with main method so that you can see your project in Launch Configuration drop down list. (Eclipse Kepler)
I am new to Java and am learning from Stanford lectures from YouTube.
So I was trying out their assignment to make a breakout game and so far so good until now. I have all my bricks, ball and paddle including the mechanics of the game but when i run the game only one brick can be removed when hit by the ball. see this. That brick happens to be the last brick added to the canvas.
The ball just flies past all other bricks with no effect. The relevant code is below.
Am I lacking some important knowledge about getElementAt here? I have a feeling that getCollidingObject is not assigned to collider, which is making the collision detection faulty. I hope someone can enlighten me on this!
private void addBallMotion(){
// y component of starting velocity; pixels per second
vy = 3.0;
/* x component of starting velocity; pixels per second
* which ranges according to the random generator
* can be negative or positive, ball can go left or right with equal chances
*/
vx = rgen.nextDouble(1.0, 3.0);
if (rgen.nextBoolean(0.5)){
vx = -vx;
}
while (true){
ball.move(vx, vy);
checkCollision();
pause(FPS);
}
}
private void checkCollision(){
checkWallCollision();
checkBrickAndPaddleCollision();
}
private void checkWallCollision(){
//checks for left or right collision
if ( (ball.getX() < leftBounds.getX() ) || (ball.getX() + BALL_RADIUS * 2 > rightBounds.getX()) ){
vx = -vx;
}
//checks for top or bottom collision
if ( (ball.getY() < topBounds.getY() ) || (ball.getY() + BALL_RADIUS * 2 > bottomBounds.getY()) ){
vy = -vy;
}
}
private void checkBrickAndPaddleCollision(){
GObject collider = getCollidingObject();
if (collider == brick){
remove(collider);
vy = -vy;
}
if (collider == paddle){
vy = -vy;
}
}
//check for collision at the 4 edges of the ball
//starting with the left and going clockwise
private GObject getCollidingObject(){
GObject ballLeft= getElementAt (ball.getX() - 1, ball.getY() + BALL_RADIUS);
GObject ballRight= getElementAt (ball.getX() + BALL_RADIUS * 2 + 1, ball.getY() + BALL_RADIUS);
GObject ballTop = getElementAt (ball.getX() + BALL_RADIUS * 2, ball.getY() - 1);
GObject ballBottom= getElementAt (ball.getX() + BALL_RADIUS, ball.getY() + BALL_RADIUS * 2 + 1);
if (ballLeft != null){
return (ballLeft);
}
else if (ballTop != null){
return (ballTop);
}
else if (ballRight != null){
return (ballRight);
}
else if (ballBottom != null){
return (ballBottom);
}
return (null);
}
private GRect paddle; // creates a paddle that only moves linearly according to mouses' x coordinate
private GRect brick;
private GOval ball;
private double vx, vy; // x and y components of the ball's velocity
//private GObject collider;
Here's the whole program:
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;
/** Offset of the side bricks from the sides of game window */
private static final int BRICK_X_OFFSET = ((WIDTH - NBRICKS_PER_ROW * (BRICK_WIDTH + BRICK_SEP) + BRICK_SEP) / 2);
/** Number of turns */
private static final int NTURNS = 3;
/** Number of frames per second */
private static final int FPS = 1;
/* Method: run() */
/** Runs the Breakout program. */
public void run() {
addMouseListeners();
addWorld();
// runGame();
}
private void addWorld(){
setSize (APPLICATION_WIDTH, APPLICATION_HEIGHT);
addPlayingBox();
addBricks();
addPaddle();
addBall();
// addCounter();
}
//adds the bound area onto screen
private void addPlayingBox(){
topBounds = new GLine (0, 0, WIDTH, 0);
bottomBounds = new GLine (0, HEIGHT, WIDTH, HEIGHT);
leftBounds = new GLine (0, 0, 0, HEIGHT);
rightBounds = new GLine (WIDTH, 0, WIDTH, HEIGHT);
add (topBounds);
add (bottomBounds);
add (leftBounds);
add (rightBounds);
}
private void addBricks(){
for (int i = 0; i < NBRICK_ROWS; i++){
int y = BRICK_Y_OFFSET + (i * (BRICK_HEIGHT + BRICK_SEP));
for (int j = 0; j < NBRICKS_PER_ROW; j++){
int x = (BRICK_X_OFFSET) + (j * (BRICK_WIDTH + BRICK_SEP));
brick = new GRect (x, y, BRICK_WIDTH, BRICK_HEIGHT );
colorBrick(brick, i);
add (brick);
}
}
}
// every consecutive 2 rows are colored the same
private void colorBrick(GRect brick, int rowNumber){
brick.setFilled (true);
switch (rowNumber + 1) {
case 1: case 2: brick.setColor(Color.red);
break;
case 3: case 4: brick.setColor(Color.orange);
break;
case 5: case 6: brick.setColor(Color.yellow);
break;
case 7: case 8: brick.setColor(Color.green);
break;
case 9: case 10:brick.setColor(Color.cyan);
break;
}
}
//adds paddle to screen
private void addPaddle(){
paddle = new GRect (PADDLE_WIDTH, PADDLE_HEIGHT);
paddle.setFilled(true);
paddle.setColor (Color.BLACK);
add (paddle);
}
//creates motion for the paddle according to mouse movement
public void mouseMoved(MouseEvent e){
paddle.setLocation ((e.getX() - PADDLE_WIDTH / 2), (double) (HEIGHT - PADDLE_Y_OFFSET));
/* checks if the paddle within the playing area
* if not the paddles will stay at the extremities*/
if ( paddle.getX() > (WIDTH - PADDLE_WIDTH)){
paddle.setLocation((double) (WIDTH - PADDLE_WIDTH), (double) (HEIGHT - PADDLE_Y_OFFSET));
}
if ( paddle.getX() < 0){
paddle.setLocation((double) 0, (double) (APPLICATION_HEIGHT - PADDLE_Y_OFFSET));
}
}
private void addBall(){
ball = new GOval (((WIDTH - BALL_RADIUS * 2) / 2), ((HEIGHT - BALL_RADIUS * 2) / 2),
BALL_RADIUS * 2, BALL_RADIUS * 2);
ball.setFilled(true);
ball.setColor(Color.BLACK);
add (ball);
addBallMotion();
}
private void addBallMotion(){
// y component of starting velocity; pixels per second
vy = 3.0;
/* x component of starting velocity; pixels per second
* which ranges according to the random generator
* can be negative or positive, ball can go left or right with equal chances
*/
vx = rgen.nextDouble(1.0, 3.0);
if (rgen.nextBoolean(0.5)){
vx = -vx;
}
while (true){
ball.move(vx, vy);
checkCollision();
pause(FPS);
}
}
private void checkCollision(){
checkWallCollision();
checkBrickAndPaddleCollision();
}
private void checkWallCollision(){
//checks for left or right collision
if ( (ball.getX() < leftBounds.getX() ) || (ball.getX() + BALL_RADIUS * 2 > rightBounds.getX()) ){
vx = -vx;
}
//checks for top or bottom collision
if ( (ball.getY() < topBounds.getY() ) || (ball.getY() + BALL_RADIUS * 2 > bottomBounds.getY()) ){
vy = -vy;
}
}
private void checkBrickAndPaddleCollision(){
GObject collider = getCollidingObject();
if (collider == brick){
remove(collider);
vy = -vy;
}
if (collider == paddle){
vy = -vy;
}
}
//check for collision at the 4 edges of the ball
//starting with the left and going clockwise
private GObject getCollidingObject(){
GObject ballLeft= getElementAt (ball.getX() - 1, ball.getY() + BALL_RADIUS);
GObject ballRight= getElementAt (ball.getX() + BALL_RADIUS * 2 + 1, ball.getY() + BALL_RADIUS);
GObject ballTop = getElementAt (ball.getX() + BALL_RADIUS * 2, ball.getY() - 1);
GObject ballBottom= getElementAt (ball.getX() + BALL_RADIUS, ball.getY() + BALL_RADIUS * 2 + 1);
if (ballLeft != null){
return (ballLeft);
}
else if (ballTop != null){
return (ballTop);
}
else if (ballRight != null){
return (ballRight);
}
else if (ballBottom != null){
return (ballBottom);
}
return (null);
}
private GRect paddle; // creates a paddle that only moves linearly according to mouses' x coordinate
private GRect brick;
private GOval ball;
private double vx, vy; // x and y components of the ball's velocity
private RandomGenerator rgen = RandomGenerator.getInstance();
//private GObject collider;
private GLine topBounds; // creates a bounding box that is the playing area
private GLine bottomBounds;
private GLine leftBounds;
private GLine rightBounds;
}
Exactly as I thought.
Look - You have a field in your class called brick. It's of GRect type. At the beginning you're calling addWorld() method, which calls addBricks(). Now check what you wrote:
private void addBricks(){
for (int i = 0; i < NBRICK_ROWS; i++){
int y = BRICK_Y_OFFSET + (i * (BRICK_HEIGHT + BRICK_SEP));
for (int j = 0; j < NBRICKS_PER_ROW; j++){
int x = (BRICK_X_OFFSET) + (j * (BRICK_WIDTH + BRICK_SEP));
brick = new GRect (x, y, BRICK_WIDTH, BRICK_HEIGHT );
colorBrick(brick, i);
add (brick);
}
}
}
What happens in there? You're having a loop in which brick is overriden NBRICK_ROWS * NBRICKS_PER_ROW times, which causes brick field to be the last created brick on your screen.
Basically, you're doing something similar to this:
int x;
x = 5;
x = 6;
x = 8;
// x is 8 now
And in checkBrickAndPaddleCollision() you're checking only if collider is brick. Which in other words means, you're checking if it's the last brick - if it is, then you remove it.
First of all you should create an array of bricks, not just one field.
ArrayList<GRect> bricks;
instead of:
GRect brick;
And then in addBricks() method, you should have:
bricks.add(new GRect (x, y, BRICK_WIDTH, BRICK_HEIGHT));
instead of:
brick = new GRect (x, y, BRICK_WIDTH, BRICK_HEIGHT );
After this, in checkBrickAndPaddleCollision(), you should not check:
if (collider == brick) {
remove(collider);
vy = -vy;
}
but rather all bricks:
for(GRect brick : bricks) {
if (collider.equals(brick)) { // Note that you should rather use .equals, instead of == as Aurand stated in his comment
remove(collider);
vy = -vy;
}
}
The problem I'm trying to solve is building a pyramid centered in the window built with individual bricks. With the code blow I expect the there should be 1 brick on the 1st row, two bricks on the 2nd row, 3 on the 3rd row all the way up to 12 bricks on the base of the pyramid. Instead only 1 brick is appearing in the center of the screen. What could I do to correct my code?
import acm.program.*;
import acm.graphics.*;
public class BrickPyramid extends GraphicsProgram{
public void run() {
/** xBrick, yBrick trying to calculate for the center of the window **/
double xBrick = (getWidth() - BRICK_WIDTH) / 2 ;
double yBrick = (getHeight() - BRICK_HEIGHT) /2 ;
/** getting the size of the brick by multiplying the brick width by brick height **/
double buildingBrick = BRICK_WIDTH * BRICK_HEIGHT;
for (int i = 0 ; i <= 12; i ++ ){
for (int j = 0; j < BRICKS_IN_BASE; j++){
double x = i * buildingBrick;
double y = j * buildingBrick;
/* adding the brick with starting point xbrick, ybrick **/
GRect brick = new GRect (xBrick, yBrick, BRICK_WIDTH, BRICK_HEIGHT);
add(brick);
}
}
}
private static final int BRICK_WIDTH = 100;
private static final int BRICK_HEIGHT = 50;
private static final int BRICKS_IN_BASE = 12;
}
You construct all of your bricks with this line executed many times:
GRect brick = new GRect (xBrick, yBrick, BRICK_WIDTH, BRICK_HEIGHT);
but you never change the values of any of the variables passed to the constructor so the bricks are all drawn in the same place.
You need to change these values for each brick. Maybe use i and j to determine those values?
don't know if you are still looking for an answer but this code does the job:
public class Pyramid extends GraphicsProgram {
private static final int BRICK_WIDTH = 30;
private static final int BRICK_HEIGHT = 12;
public void run() {
double xBrick = (getWidth() - BRICK_WIDTH) / 2 ;
double yBrick = (getHeight() - BRICK_HEIGHT) /2 ;
for (int n=1;n<15;n++){
for (int m=0;m<=n-1;m++){
GRect Brick =new GRect(xBrick-(BRICK_WIDTH*(n-1))/2+m*BRICK_WIDTH,yBrick+BRICK_HEIGHT*(n-1),BRICK_WIDTH,BRICK_HEIGHT);
add(Brick);
}
}
}
}
I'm trying to learn java by following the cs106a course online.
Currently I've arrived at the breakout exersize and I'm having some trouble with bugs
I haven't finished it completely but I want to solves these issues first before I go further.
Problem 1:
when the ball collides with the bricks they don't always seem to be removed from the canvas.
sometimes the brick will be removed on a second time it collides. But there is one row (of yellow bricks) that doesn't respond at all to the ball collisions.
Problem 2:
my paddle can be moved by dragging the mouse. the only problem is. the bricks can also be moved like the paddle.
I know it has something to do with this piece of code
gobj = getElementAt(lastX, lastY);
If I remove it altogether the bricks aren't moveable anymore. which is good. but I'm still
able to move the paddle no matter where I click and drag.
can anyone give me a hint so I can correct the mistakes?
Here's my code below.
Thanks
/*
* 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.*;
import org.omg.CORBA.PUBLIC_MEMBER;
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 = 8;
/** 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;
private static final int DELAY = 50;
private static final double X_START = WIDTH / 2;
private static final double Y_START = 450;
public void run() {
world();
play();
}
private void ball() {
ball = new GOval(X_START, Y_START, BALL_RADIUS, BALL_RADIUS);
ball.setFillColor(Color.BLACK);
ball.setFilled(true);
add(ball);
}
private void paddle() {
paddle = new GRect(100, 500, PADDLE_WIDTH, PADDLE_HEIGHT);
paddle.setColor(Color.BLACK);
paddle.setFilled(true);
add(paddle);
}
private void brick(int x, int y, Color c) {
GRect brick = new GRect(x, y, BRICK_WIDTH, BRICK_HEIGHT);
brick.setColor(getBackground());
brick.setFillColor(c);
brick.setFilled(true);
add(brick);
}
private void brickRow(int x, int y, Color c) {
x = BRICK_SEP / 2;
y += BRICK_Y_OFFSET;
for (int i = 0; i < NBRICKS_PER_ROW; i++) {
brick(x, y, c);
x += BRICK_WIDTH + BRICK_SEP;
}
}
private void world() {
//initialize x and y position for the rows of bricks
int x = 0;
int y = 0;
// set starting color for first row
Color c = Color.red;
paddle();
//create 2 rows of bricks and switch colors
for (int i = 0; i < NBRICK_ROWS; i++) {
if (i <= 1) {
c = Color.ORANGE;
} else if (i > 1 && i <= 3) {
c = Color.YELLOW;
} else if (i > 3 && i <= 5) {
c = Color.GREEN;
} else if (i > 5 && i <= 7) {
c = Color.CYAN;
}
brickRow(x, y, c);
y += BRICK_HEIGHT + BRICK_SEP;
}
}
private void moveBall() {
ball.move(xVel, yVel);
}
public void mousePressed(MouseEvent e) {
lastX = e.getX();
lastY = e.getY();
gobj = getElementAt(lastX, lastY);
}
public void mouseDragged(MouseEvent e) {
if (paddle != null) {
paddle.move(e.getX() - lastX, getY());
lastX = e.getX();
lastY = e.getY();
}
//constrain paddle movement
if (paddle.getX() < 0){
paddle.setLocation(0, 500);
}else if (paddle.getX()+BRICK_WIDTH > WIDTH ){
paddle.setLocation(WIDTH-BRICK_WIDTH, 500);
}
}
private void checkForCollision() {
// ball collission with walls
if (ball.getY() > getHeight() - BALL_RADIUS
|| ball.getY() < 0 + BALL_RADIUS) {
yVel = -yVel;
} else if (ball.getX() > getWidth() - BALL_RADIUS
|| ball.getX() < 1 + BALL_RADIUS) {
xVel = -xVel;
// ball collission with paddle
} else if (getElementAt(ball.getX() + BALL_RADIUS, ball.getY()
+ BALL_RADIUS) == paddle) {
yVel = -yVel;
// check for collision with bricks to remove them but ignore collision with paddle
} else if (getElementAt(ball.getX(), ball.getY()) != null
&& getElementAt(ball.getX(), ball.getY()) != paddle) {
remove(getCollidingObject(ball.getX(), ball.getY()));
yVel = -yVel;
} else if (getElementAt(ball.getX() + BALL_RADIUS, ball.getY()
+ BALL_RADIUS) != null
&& getElementAt(ball.getX() + BALL_RADIUS, ball.getY()
+ BALL_RADIUS) != paddle) {
remove(getCollidingObject(ball.getX() + BALL_RADIUS, ball.getY()
+ BALL_RADIUS));
yVel = -yVel;
} else if (getElementAt(ball.getX() + BALL_RADIUS, ball.getY()
+ BALL_RADIUS) != null
&& getElementAt(ball.getX() + BALL_RADIUS, ball.getY()
+ BALL_RADIUS) != paddle) {
remove(getCollidingObject(ball.getX() + BALL_RADIUS, ball.getY()
+ BALL_RADIUS));
yVel = -yVel;
} else if (getElementAt(ball.getX(), ball.getY() + BALL_RADIUS) != null
&& getElementAt(ball.getX(), ball.getY() + BALL_RADIUS) != paddle) {
remove(getCollidingObject(ball.getX(), ball.getY() + BALL_RADIUS));
yVel = -yVel;
}
}
private void play() {
addMouseListeners();
ball();
while (true) {
checkForCollision();
moveBall();
pause(DELAY);
}
}
private GObject getCollidingObject(double x, double y) {
return getElementAt(x, y);
}
private double lastX;
private double lastY;
private double xVel = 5;
private double yVel = 15;
private GOval ball;
private GRect paddle;
private GObject gobj;
}
Why don't you use suggested method in handout 19:
private GObject getCollidingObject()
use also collider:
GObject collider = getCollidingObject();
First of all separate your conditions with walls collisions and (paddle with bricks) game elements collisions into two separate methods cause to simplify else if cascading (decomposition I think you know what it means). You have only two objects what you should worry about paddle and bricks, so you need to compare only on (collider == paddle) and collider != null. Also you use only radius in comparisons but should use diameter (2 * radius). Have fun :)
You're y step for each movement of the ball is too large at 15. Since each brick is only 8 in height, at times you're bypassing the brick before evaluating whether that location contained an object or not. E.g. Lets assume you're last evaluation left you're ball with a Y location of 20 and the closest brick is only length of 2 away. You're next evaluation will not include this brick since you move 15 and the height of the brick is 8, meaning range of y values 22 to 30. You'll be out by 5 away from this brick in you're next evaluation. I know the values should be inverted considering the location of the bricks, but you get the point...
With smaller steps in y values collisions will be checked more regularly and provided you make the step small enough, remove this problem.