JFrame getHeight() и getWidth() возвращают 0

Я делаю простую игру в понг; и часть механики столкновения требует получения ширины и высоты холста, чтобы перенаправить мяч. Однако getWidth() и getHeight() почему-то возвращают 0. Вот основной блок кода.

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);
        }
    }
}

А вот настоящий класс Ball, который обрабатывает условия движения.

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);
    }
} 

РЕДАКТИРОВАТЬ: проблема решена, я просто не сказал getWidth() и getHeight(), на что ссылаться. Очевидно, что если я не скажу им, что нужно получить, они вернут null. Дерп. Простое исправление состояло в том, чтобы изменить их на game.getWidth() и game.getHeight(). Спасибо за помощь, однако! Все ваши материалы помогают и в других областях. :)


person Googly_    schedule 05.02.2014    source источник
comment
Не переопределяйте paint, вместо этого используйте paintComponent. Убедитесь, что вы вызываете super.paintXxx для поддержания цепочки рисования. Остерегайтесь использования бесконечных циклов в Swing, поскольку они могут помешать перерисовке приложения. Swing Timer был бы более безопасным или явным Thread. Не полагайтесь на магические числа (после исправления) используйте getWitdh и getHeight для определения области, которая должна быть закрашена   -  person MadProgrammer    schedule 06.02.2014
comment
Небольшой пример, для переопределения getPreferredSize() прочитайте указанную в нем ссылку для получения дополнительной помощи :-)   -  person nIcE cOw    schedule 06.02.2014


Ответы (2)


  1. Graphics/Java2D по умолчанию никогда не возвращает разумные Dimension, результат равен нулю Dimension, вы должны переопределить getPreferredSize для JPanel, тогда getWidth/Height вернет правильные координаты для размера JPanels.

  2. затем использовать JFrame.pack() вместо любого размера.

  3. переопределить paintComponent для Swing JComponents вместо paint() внутри paintComponent 1-го. строка кода должна быть super.paintComponent, иначе рисование накапливается.

  4. никогда не использовать Thread.sleep(int) в Swing, а также для пользовательского рисования или анимации в Java7 и Swing, используйте Swing Timer вместо бесконечного цикла, остановленного Thread.sleep(int).

person mKorbel    schedule 05.02.2014
comment
Не могли бы вы подробнее объяснить, как переопределить getPreferedSize? - person Googly_; 06.02.2014

Это сложная проблема, усугубляемая некоторыми дизайнерскими решениями.

Обычно Swing хочет, чтобы компоненты использовались в рамках ограничений менеджера компоновки. Менеджерам компоновки требуется определенный объем информации, чтобы принимать решения о том, как лучше всего компоновать эти компоненты. Это включает getPreferred/Minimum/Maximum размер.

Проблема в том, что вы на самом деле не хотите использовать менеджеры компоновки, так как хотите позиционировать Ball вручную. Когда вы выбрасываете менеджер компоновки, вы берете на себя большую часть работы, гарантируя, что размер и положение компонента будут правильными в родительском контейнере.

Более простым решением было бы создание «игровой» поверхности, на которой вы можете рисовать игровые элементы или сущности.

Проблема в том, что вы, кажется, не знаете, какой подход вы хотите использовать. Ваш Ball простирается от JPanel, но ваша панель Main рисует сама себя... это не то, как должны отображаться компоненты.

Вместо того, чтобы использовать компоненты в качестве основы для игровых сущностей, что приводит к многочисленным проблемам и накладным расходам на управление, вы можете просто использовать Main в качестве основной игровой поверхности и отображать на ней все сущности напрямую. Это дает вам больший контроль и упрощает процесс - ИМХО

Например...

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);
        }
    }
}
person MadProgrammer    schedule 05.02.2014