JPanel width exceeding the dimension defined in getPreferredSize ()

3

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

Here is my class Board , 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 overwritten the preferredSize() method to comply with these measures.

The problem is that all logic (collisions with screen edges) is based on these measures that I set in JPanel , but for some reason there is getting an additional space on the right, as can be seen by the collision of the ball and the bat in the right corner in the gif below:

Ithoughttheproblemmightberelatedto this other question , but extra space occurs within JPanel and has no relation to JFrame .

In the Ball class, the formula I'm using to detect the right corner boundary and 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 do I remove this space by keeping the size of the JPanel fixed?

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

    
asked by anonymous 11.05.2017 / 16:33

2 answers

4

As per analog question answer in SOEn, the actual spacing existed, due to the fact that I invoke the pack() , which is responsible for sizing the window and all its components, and then changing frame definitions, such as setResizable(false) , in the class that starts the application:

public void initGui() {
    frame = new JFrame("Tennis Game");

    frame.setJMenuBar(getMenu());

    this.boardGame = new Board();
    frame.setContentPane(boardGame);
    frame.pack();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setLocationRelativeTo(null);
    frame.setResizable(false); //esta chamada após o pack() é que ocasiona o erro
    frame.setVisible(true);
}

When calling setResizable(false) , the extra space dedicated to system borders to resize the window is no longer necessary, and this space ends up being added to the size of the Frame, resulting in the space beyond what I set for JPanel .

The solution was to only pass the call of pack() as the last one, preceding just setvisible :

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

By just changing this throughout the code, the problem of ball collision and Paddle works as expected:

Theideaofaddingarelativesizemodifiersuggestedby brow-joe was also good and effective, but the problem was a small mistake in order to call the methods that render the application.

    
12.05.2017 / 14:48
1

Logic is right, there is only one condition in the move method of Ball that can be changed, actually when you do:

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

It's getting a "leftover" of 30px before actually colliding with x on the side.

I recommend using a percentage of the total diameter as follows:

double frequency = 0.5;
if (x >= board.WIDTH - (DIAMETER * frequency) || x <= 0) {
    ...
}

The frequency is the percentage of 0 to 1, the closer to 0 the greater the excess of pixels and the closer to 1 the smaller the amount of pixels

You will need to do the same as Paddle , however as its size is larger, you can use a higher frequency.

for example in method move of Paddle

double frequency = 0.9;
if ((x * frequency) + direction >= 0 && x + direction <= board.WIDTH - (WIDTH * frequency)) {
    ...
}

Follow the gif with the settings of 50% (0.5) in Ball and with 90% (0.9) in Paddle

    
11.05.2017 / 17:12