Jpanel width exceeding the dimension defined in getPreferredSize()

Asked

Viewed 213 times

3

I’m trying to create a little game Breakout, I’ve done a lot of logic, only I discovered an uncomfortable problem related to the size of the panel of the "scene" of the game.

Follow my class Board, which is the panel where all game components 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 above code, it can be seen that I set width and height based on fixed values, and override the method preferredSize() to ensure that these measures are complied with.

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

inserir a descrição da imagem aqui

I thought the problem might be related to this other doubt, but the extra space occurs within the JPanel and has nothing to do with the JFrame.

In class Ball, the formula I’m using to detect the right corner boundary and reverse the direction is in the method move() 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 while keeping the size of the Jpanel fixed?

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

2 answers

4


In accordance with answer analogous question in Soen, the spacing actually existed, due to the fact that I invoked the method pack(), which is responsible for resizing the window and all its components, and then changing frame settings 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);
}

By 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 space beyond what I set for the JPanel.

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

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

Just changing this throughout the code, the problem of ball collision and the Paddle function as expected:

inserir a descrição da imagem aqui

The idea of adding a relative size modifier suggested by Brow-joe was also good and effective, but the cause of the problem was a little mistake in the order of calling the methods that render the application.

1

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

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

is getting a 30px "leftover" before actually happening the x collision with 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 from 0 to 1, the closer to 0 the higher the pixel leftovers and the closer to 1 the lower the pixel leftovers

You’ll need to do the same in Paddle, but as its size is larger, you can use a higher frequency.

for example in the method move of Paddle

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

Follow gif with 50% (0.5) settings on Ball and 90% (0.9) in Paddle inserir a descrição da imagem aqui

Browser other questions tagged

You are not signed in. Login or sign up in order to post.