1

I'm trying to create a breakout game, but I discovered an annoying problem regarding the size of the "scene" panel of the game.

Here is my Board class, which is the panel where all the components of the game are distributed:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class Board extends JPanel {

    private static final long serialVersionUID = 1L;
    public final int WIDTH = 400;
    public final int HEIGHT = 300;
    private final int UPDATE_INTERVAL = 20;

    private Timer timer;
    public Ball ball;
    public Paddle paddle;

    private JLabel scoreLabel, score;   

    public Board() {

        paddle = new Paddle(this);
        ball = new Ball(this);

        setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));

        scoreLabel = new JLabel("Score: ");
        scoreLabel.setFont(getFont().deriveFont(12f));
        score = new JLabel();
        score.setFont(getFont().deriveFont(12f));
        this.add(scoreLabel);
        this.add(score);

        this.addKeyListener(new KeyAdapter() {

            @Override
            public void keyPressed(KeyEvent e) {
                paddle.keyPressed(e);
                if (e.getKeyCode() == KeyEvent.VK_SPACE){
                    starGame();
                }
            }

            @Override
            public void keyReleased(KeyEvent e) {
                paddle.keyReleased(e);
            }
        });

        ActionListener action = new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent arg0) {
                updateBoard();
                repaint();
            }
        };

        timer = new Timer(UPDATE_INTERVAL, action);

        setFocusable(true);

    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(WIDTH, HEIGHT);
    }

    private void updateBoard() {
        ball.move();
        paddle.move();
        repaint();
    }

    public void gameOver() {
        JOptionPane.showMessageDialog(this, "Game Over");
        newGame();
    }

    public void starGame() {
        timer.start();
    }

    public void stop() {
        timer.stop();
    }

    public void newGame() {
        stop();
        paddle = new Paddle(this);
        ball = new Ball(this);
        repaint();
    }

    public void setSpeed(int speed) {
        ball.setSpeed(speed);
    }


    @Override
    protected void paintComponent(Graphics g) {

        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        ball.paint(g2);
        paddle.paint(g2);
    }
}

In the code above, it can be seen that I set width and height based on fixed values, and I overridden the preferredSize() method to comply with these measures.

The problem is that the collision logic with screen edges is based on those measurements that I set in JPanel, but for some reason it's getting an additional space on the right, as can be seen by the collision of the ball and Paddle in the right corner in the gif below:

enter image description here

I thought the problem might be related to this other doubt, but the extra space occurs inside the JPanel.

In the Ball class, the formula I'm using to detect the right corner boundary to reverse direction is in the move() method of both classes:

import java.awt.Graphics2D;
import java.awt.Rectangle;

public class Ball {

    private int x = 0;
    private int y = 15;
    private final int DIAMETER = 30;
    private int xSpeed = 1;
    private int ySpeed = 1;

    private final Board board;
    private final Paddle paddle;


    public Ball(Board board) {
        this.board = board;
        this.paddle = board.paddle;
        y = paddle.getTopY() - DIAMETER;
        x = board.WIDTH / 2 - DIAMETER / 2;
    }

    public void move() {

        if (x >= board.WIDTH - DIAMETER || x <= 0) {
            xSpeed = -xSpeed;
        }

        if (y < 15) {
            ySpeed = -ySpeed;
        }

        if (y + DIAMETER > paddle.getTopY() + paddle.HEIGHT) {
            board.gameOver();
        }

        if (collision()) {

            float paddleCenter = paddle.getX() + (paddle.WIDTH/2);

            float relativePos = (this.x + (DIAMETER/2) - paddleCenter) / (paddle.WIDTH/2);

            if((relativePos > 0 && xSpeed < 0) || (relativePos < 0 && xSpeed > 0)){
                xSpeed = -xSpeed;
            }

            ySpeed = -ySpeed;
            y = paddle.getTopY() - DIAMETER;
        }

        x += xSpeed;
        y += ySpeed;
    }

     [...]
}

Class Paddle:

import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;

public class Paddle {

    private int x = 0;
    private final int topY;
    public final int WIDTH = 100;
    public final int HEIGHT = 10;
    private int direction = 0;

    private Board board;

    public Paddle(Board board) {
        this.board = board;
        topY = board.HEIGHT;
        x = board.WIDTH / 2 - WIDTH / 2;

    }

    public void move() {
        if (x + direction >= 0 && x + direction <= board.WIDTH - WIDTH) {
            x = x + direction;
        }
    }

    public void paint(Graphics2D g2) {
        g2.fillRect(x, topY, WIDTH, HEIGHT);
    }

    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_LEFT) {
            direction = -5;
        }
        if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
            direction = 5;
        }
    }

    public void keyReleased(KeyEvent e) {
        direction = 0;
    }

    public Rectangle getBounds() {
        return new Rectangle(x, topY, WIDTH, HEIGHT);
    }

    public int getTopY() {
        return topY;
    }

    public int getX() {
        return x;
    }
}

How to remove this space by keeping the size of the JPanel fixed?

As a testable snippet would be large in the question, I added in the Gist an executable code containing all the classes (Ball, Paddle and Board) involved.

Community
  • 1
  • 1
  • 1
    `As a testable snippet would be large in the question,` - why? The Paddle class is irrelevant to the question. All you need is the Board, the Ball and the frame. The menubar is also irrelevant. The point of the [mcve] is for you to simplify the code so you can concentrate on the logic directly related to the problem. – camickr May 11 '17 at 15:14
  • 1
    `The example in gist is Minimal, Complete, and Verifiable example, just` - no that is a dump of your entire application. Your problem is about the Ball and how it bounces against the panel sides. The other code is irrelevant to your question. I already gave you examples of unnecessary code. The point of the `MCVE` is to simplify the problem. We don't have time to look at hundreds of lines of code. So you simplify the code so that it still remains executable. – camickr May 11 '17 at 17:30
  • @camickr The same problem affect the Paddle class too. For this reason I added the Ball, Paddle and Board class. There is more code in all three classes, which I removed to simplify execution. – Blastoise Opressor May 11 '17 at 17:37
  • 1
    Just for future reference FlowLayout is the default layout of JPanels, no need to set it to that. – Michael May 11 '17 at 19:59

1 Answers1

1

Well we don't know the context of how your application is built. For example do you add the Board panel directly to the frame? Or maybe you are nesting the Board panel into another panel and the extra space you are seeing is from the parent panel?

Did you try adding a LineBorder to the Board panel. If the Border paints correctly and the ball still doesn't reach the edge then you have a problem with your collision logic.

setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));

That doesn't make sense since you are doing custom painting of the ball and paddle. If anything the layout should be null.

Edit:

This looks suspicious:

frame.pack(); 
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
frame.setLocationRelativeTo(null); 
frame.setResizable(false); 
frame.setVisible(true);

You pack() the frame but then change a property of the frame. Because the frame is not resizable there is no need to paint the border so the extra width of the Border is now added to the panel but your logic checks a hardcoded value, not the actual width of the panel.

The code should be:

//frame.pack(); 
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
frame.setLocationRelativeTo(null); 
frame.setResizable(false); 
frame.pack(); 
frame.setVisible(true);

Note how the problem is NOT related to the code you posted.

This is why a proper MCVE should be posted in the forum with EVERY question, not on another website!!!

camickr
  • 321,443
  • 19
  • 166
  • 288
  • The Board class is added as the "contentpane" of JFrame. Just have this JPanel in the window. And I added 2 JLabels for score in the JPanel, so I used this Layout for this. – Blastoise Opressor May 11 '17 at 15:19
  • Well the labels should probably be on a separate panel added to the PAGE_START of the frame. In any case if you verified the Border of the panel is correct, then as I stated the problem is your collision detection logic. So you just need to do basic debugging. Step through the logic making sure the value of all your variable is what you expect them to be. You obviously have a boundary issue somewhere. – camickr May 11 '17 at 15:32
  • 1
    @diegofm, You can also check out: http://stackoverflow.com/questions/42678570/java-random-placement-of-objects/42679448#42679448 for some ball animation. Just change the number of balls to 1, to see how it reacts. The edge of the ball always touches the side of the panel. You can even resize the frame. So maybe the logic used there will help find your logic problem. – camickr May 11 '17 at 15:48
  • The logic I'm using in move method is identical to yours in this code. – Blastoise Opressor May 11 '17 at 16:29
  • For testing, I drew a small rectangle inside the JPanel with width = 400 and there is a space left. The same space ignored by the ball. – Blastoise Opressor May 11 '17 at 16:32
  • @diegofm the logic can't be identical, since my logic is based on the dynamic width of the panel. I didn't suggest drawing a rectangle. I suggested adding a LineBorder. This will show you the exact bounds of your panel. If the ball bounces against the Border, then the code is working correctly, and the problem is that you are nesting your panel in another panel and the gap you see is from the parent panel. Since you won't post a proper [mcve] you need to solve the issue on your own. – camickr May 11 '17 at 17:07
  • Sorry, I didn't understand you asked to add border. I will test – Blastoise Opressor May 11 '17 at 17:13
  • The example in gist is [mcve], just compile e execute. I did not understand what another example I should provide. – Blastoise Opressor May 11 '17 at 17:21
  • It will no longer be necessary. The problem was this! Thank you for helping me, sorry for anything. – Blastoise Opressor May 11 '17 at 17:59