I struggle with JPanel, getWidth and getHeight

So I began this “Project” yesterday, I wanted to try out a program where a cube collided with the windows border. Seemed quite simple.

However, I struggle with the borders. I cant seem to get the “perfect” collison on the right side and bottom side of the window. The cube always goes a little further than expected.

I would assume the problem has something to do with my way of setting the size of the window? I am not certain but I did try some changes but it didn’t work.

Here’s the code:

package spritesInJava;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.ImageObserver;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class Game {

public static JFrame myGame;
public static Timer timer;

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {


        public void run() {
            createGame();
            timer.start();

        }


    });

}
public static void createGame() {

    myGame = new JFrame("Felicia");
    myGame.add(new MyPanel());
    myGame.pack();
    myGame.setSize(new Dimension(1000,1000));
    myGame.setPreferredSize(new Dimension(1000,1000));
    myGame.setVisible(true);
    myGame.setDefaultCloseOperation(1);
    MyPanel.loadImage();
}


}
class MyPanel extends JPanel implements ActionListener {

private static Image image; //Sprite
private static final long serialVersionUID = 1251340649341903871L;
private static ImageObserver observer;
private  int x = 50;
private int y = 50;
private int width = 200;
private int height = 100;
private int speedx = 2;
private int speedy = 2;

MyPanel() {

    Game.timer = new Timer(10,this);


}
@Override
public void actionPerformed(ActionEvent e) {

    int screenWidth = Game.myGame.getWidth();
    int screenHeight = Game.myGame.getHeight();
    x+=speedx;
    y+=speedy;

    if(x >= screenWidth - width) {
        speedx = -speedx;

    }
    else if(x < 0 ) {
        speedx = -speedx;


    }

    if(y >= screenHeight- height ) {

        speedy = -speedy;
    }
    else if(y < 0 ) {
        speedy = -speedy;

    }

    repaint();

}
public void paintComponent(Graphics g) {
    super.paintComponent(g);
    //g.drawImage(image, x, y,width,height,observer);   //Uses the image that was loaded below and draws it.
    g.fillRect(x, y, width, height);
}

public static void loadImage() {    //Load the image
    ImageIcon icon = new ImageIcon("photos/Felicia.png");
    image = icon.getImage();    //Set the "temp" ImageIcon icon to the public ImageIcon
}


}

The cube is supposed to hit one of the 4 borders and then change to “-speed”, so if speed is 5 the speed shall be the total opposite (negative) and turn the other way. This works, but it does it a little bit too late. It seems like the collision system I made doesn’t work

Sorry for putting all the code in one class!

Thanks in advance!

Answer

So, immediately, this seems wrong…

myGame.add(new MyPanel());
myGame.pack();
myGame.setSize(new Dimension(1000, 1000));
myGame.setPreferredSize(new Dimension(1000, 1000));
myGame.setVisible(true);

You pack the window before the preferred size has been set/determined, meaning that the window will want to assume the smallest size possible (as the preferredSize of the content is 0x0 by default).

You should avoid calling setPreferredSize as a general rule, but I would especially avoid using it on a JFrame.

Why? Because a JFrame has decorations (window title and borders), which are inset into the window, so the viewable size is actually frameSize - decorationInsets, which becomes the core of your problem later.

So instead, I wold override getPreferredSize in your MyPanel and specify the “viewable” range of the component

class MyPanel extends JPanel implements ActionListener {

    //...

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

Then in you main class you can just do…

myGame = new JFrame("Felicia");
myGame.add(new MyPanel());
myGame.pack();
myGame.setVisible(true);

And the API will work it out for you.

Ok, onto the next issue…

public void actionPerformed(ActionEvent e) {

    int screenWidth = Game.myGame.getWidth();
    int screenHeight = Game.myGame.getHeight();

Game.myGame is a JFrame, you’re asking for the window size, but as I said earlier, frames have decorations, which are inset into the frame, reducing the viewable area available to your component. Instead, you should be using the size of the component itself

public void actionPerformed(ActionEvent e) {

    int screenWidth = getWidth();
    int screenHeight = getHeight();

Some suggestions…

I would argue the need for these…

public static JFrame myGame;
public static Timer timer;

But I have an issue with using static ;). Making myGame static makes it to “easy” to access it’s functionality from locations in your code which don’t have any responsibility for doing so.

The Timer in this context is some what arguable, but I might be tempted to have a “update engine” which encapsulated it and the over update workflow, but that’s me 😉

I would argue that this is not required (to be static)

public static void loadImage() {    //Load the image
    ImageIcon icon = new ImageIcon("photos/Felicia.png");
    image = icon.getImage();    //Set the "temp" ImageIcon icon to the public ImageIcon
}

The functionality should be created out by the component itself when it’s created (or via some other “setup” phase). I would also encourage the use of ImageIO.read as it will generate a IOException when something goes wrong, unlike ImageIcon which will fail silent.

As a side benefit (of ImageIO.read) you will also know when the image has completed loading, as the method won’t return until it does, meaning you don’t risk having some empty frames.

You don’t need…

private static ImageObserver observer;

Nor does it need to be static (as been private it kind of defeats any benefit), but, JPanel is actually an ImageObserver, so you could just pass this to drawImage

Leave a Reply

Your email address will not be published. Required fields are marked *