Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JFrame getHeight() and getWidth() return 0

I'm making a simple pong game; and part of the collision mechanics require getting the width and height of the canvas to redirect the ball. However, getWidth() and getHeight() return 0 for some reason. Here's the main block of code.

package pong;

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

public class Main extends JPanel {

    static int gameSpeed = 10;
    Ball ball = new Ball(this);

    private void move() {
        ball.move();
    }

    public void paint(Graphics g) {
        super.paint(g);
        Graphics2D g2d = (Graphics2D) g;
        ball.paint(g2d);
    }

    public static void main(String args[]) throws InterruptedException {
        JFrame frame = new JFrame("Pong");
        Main game = new Main();
        frame.add(game);
        frame.setSize(400, 400);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        while (true) {
            game.move();
            game.repaint();
            Thread.sleep(gameSpeed);
        }
    }
}

And here's the actual Ball class, which handles movement conditions.

package pong;

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

public class Ball extends JPanel {

    int x = 1;
    int y = 1;
    int dx = 1;
    int dy = 1;
    private Main game;

    public Ball(Main game) {
        this.game = game;
    }

    void move() {
        System.out.println(getWidth() + getHeight());

        if (x + dx < 0) {
            dx = 1;
        }
        if (y + dy < 0) {
            dy = 1;
        }
        if (x + dx > (getWidth() - 30)) {
            dx = -1;
        }
        if (y + dy > (getHeight() - 30)) {
            dy = -1;
        }
        x = x + dx;
        y = y + dy;
    }

    public void paint(Graphics2D g) {
        g.fillOval(x, y, 30, 30);
    }
} 

EDIT: Problem solved, I simply didn't tell getWidth() and getHeight() what to refer to. Obviously, if I don't tell them what to get, they'll return null. Derp. The simple fix was to change them to game.getWidth() and game.getHeight(). Thanks for the help, though! All your inputs help in other areas as well. :)

like image 346
Googly_ Avatar asked Feb 05 '14 20:02

Googly_


2 Answers

  1. Graphics/Java2D by default never returs reasonable Dimension, result is zero Dimension, you have to override getPreferredSize for JPanel, then getWidth/Height will return correct coordinates for JPanels' size.

  2. then to use JFrame.pack() instead of any sizing.

  3. override paintComponent for Swing JComponents, instead of paint(), inside paintComponent 1st. code line should be super.paintComponent, otherwise painting cumulated.

  4. never to use Thread.sleep(int) in Swing, nor for Custom painting or animations in Java7 and Swing, use Swing Timer instead endless loop stopped by Thread.sleep(int).

like image 105
mKorbel Avatar answered Sep 22 '22 10:09

mKorbel


This is a complex issue made worse by some design decisions.

Normally, Swing wants components to be used within the constraints a layout manager. Layout managers require a certain amount of information in order to make decisions about how best layout these components. This includes getPreferred/Minimum/Maximum size.

The problem you have is you don't actually want to use layout managers, as you want to position the Ball manually. When you throw out the layout manager, you become responsible for a lot of work, ensuring that the component is size and position correctly within the parent container.

A simpler solution would be to generate a "game" surface onto which you can paint game elements or entities.

The problem is, you don't seem to know which approach you want to use. Your Ball extends from JPanel, but your Main panel is painting itself...this is not how components should be rendered.

Instead of using components as a basis for game entities, which introduces numerous issues and management overhead, you could simply use Main as the primary gaming surface and render all the entities to it directly. This gives you greater control and simplifies the process - IMHO

For example...

import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Main extends JPanel {

    public static final int GAME_SPEED = 40;
    Ball ball = new Ball();

    protected void move() {
        ball.move(this);
    }

    public Main() {
        setLayout(null);
        Timer timer = new Timer(GAME_SPEED, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                move();
                repaint();
            }
        });
        timer.start();
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g); 
        ball.paint((Graphics2D)g);
    }

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

    public static void main(String args[]) throws InterruptedException {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Pong");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                Main game = new Main();
                frame.add(game);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            }
        });
    }

    public interface Entity {

        public void paint(Graphics2D g2d);
        public Rectangle getBounds();

    }

    public interface MovableEntity extends Entity {

        public void move(Container parent);

    }

    public class Ball implements MovableEntity {

        private int dx = 1;
        private int dy = 1;

        private int x;
        private int y;

        @Override
        public void move(Container parent) {

            int width = parent.getWidth();
            int height = parent.getHeight();

            int x = getX();
            int y = getY();

            x += dx;
            y += dy;

            if (x + dx < 0) {
                dx = 1;
            }
            if (y + dy < 0) {
                dy = 1;
            }
            if (x + dx > (width - getBounds().width)) {
                dx = -1;
            }
            if (y + dy > (height - getBounds().height)) {
                dy = -1;
            }

            setLocation(x, y);

        }

        @Override
        public void paint(Graphics2D g2d) {
            int width = getBounds().width;
            int height = getBounds().height;
            int dim = Math.min(width, height);

            int xPos = x + ((width - dim) / 2);
            int yPos = y + ((height - dim) / 2);

            g2d.fillOval(xPos, yPos, dim, dim);
        }

        @Override
        public Rectangle getBounds() {
            return new Rectangle(x, y, 30, 30);
        }
    }
}
like image 26
MadProgrammer Avatar answered Sep 20 '22 10:09

MadProgrammer