为什么在绘制多个对象时 JFrame 会出现故障?

Why does the JFrame glitch when multiple objects are painted?

我正在制作小行星游戏。每隔一段时间,需要生成一颗小行星并飞过屏幕。由于某种原因,当创建了超过 1 个小行星时,屏幕会出现故障。如果您最大化屏幕,您将能够看到故障。我试过使用 paint 而不是 paintComponent。我也尝试过扩展 JFrame 而不是 JPanel,但这只会让情况变得更糟。下面的 class 设置屏幕并处理游戏循环

public class Game extends JPanel {
    static ArrayList<Asteroids> rocks = new ArrayList<Asteroids>();

    //This variable determines whether the game should keep running
    static boolean running = true;

    //Counter to access arraylist
    static int counter = 0;

    public static void main(String[] args) throws InterruptedException {
        //Creating the window
        JFrame frame = new JFrame("Asteroid Game");
        frame.getContentPane().setBackground(Color.BLACK);
        frame.setSize(1100, 1000);
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);  

        Asteroids a = new Asteroids();
        frame.add(a);

        //Game loop
        while(running) {
            if(counter % 4 == 0) {
                rocks.add(new Asteroids());
                frame.add(rocks.get(rocks.size() - 1));
            }

            for(int i = 0; i < rocks.size(); i++) {
                rocks.get(i).repaint();
                rocks.get(i).move();
                if(!rocks.get(i).isPosFine()) {
                    rocks.remove(i);
                    i--;
                }
            }



            Thread.sleep(17);
            counter++;

        }
    }
}

下面的class设置了小行星

public class Asteroids extends JPanel {
    //These arrays store the coordinates of the asteroid
    private int[] xPos = new int[8];
    private int[] yPos = new int[8];

    //Determines whether asteroid should be generated from top or bottom
    private int[] yGen = {-100, 1100};

    //Determines the direction the asteroid shold go
    int genLevel;

    /**
     * @param g Graphics
     * This method paints the asteroid
     */
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D r = (Graphics2D)g;
        r.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        r.setColor(Color.decode("#52575D"));
        r.fillPolygon(xPos, yPos, 8);
    }

    /**
     * This constructor sets up the asteroid location points
     */
    public Asteroids() {
        int x = (int)(Math.random() * (700 - 1) + 100);
        int y = yGen[(int)(Math.random() * (1 + 1 - 0))];
        updateAsteroids(x, y);
        genLevel = y;
        System.out.println("Created!");
    }

    /**
     * @param x int
     * @param y int
     * This method generates the asteroid based on the points passed in
     */
    public void updateAsteroids(int x, int y) {
        xPos[0] = x;
        xPos[1] = x + 20;
        xPos[2] = x + 40;
        xPos[3] = x + 35;
        xPos[4] = x + 40;
        xPos[5] = x + 4;
        xPos[6] = x - 16;
        xPos[7] = x - 20;

        yPos[0] = y;
        yPos[1] = y + 7;
        yPos[2] = y + 20;
        yPos[3] = y + 40;
        yPos[4] = y + 80;
        yPos[5] = y + 70;
        yPos[6] = y + 40;
        yPos[7] = y;
    }

    /**
     * This moves the asteroid
     */
    public void move() {
        int moveSpeedx = (int)(Math.random() * (10 - 1) + 1);
        int moveSpeedy = (int)(Math.random() * (10 - 1) + 1);
        for(int i = 0; i < 8; i++) {
            if(genLevel > 0) {
                xPos[i] -= moveSpeedx;
                yPos[i] -= moveSpeedy;
            }
            else {
                xPos[i] += moveSpeedx;
                yPos[i] += moveSpeedy;
            }

        }
    }

    /**
     * @return if the asteroid should be kept on the screen or not 
     */
    public boolean isPosFine() {
        for(int i = 0; i < 8; i++) {
            if(xPos[i] > 1250 || xPos[i] < -150)
                return false;
            if(yPos[i] > 1250 || yPos[i] < - 150)
                return false;
        }
        return true;
    }
}```

我能看到你最大的问题是你让你的 Asteroids class 扩展了 JPanel,使它的重量比应有的重得多,并且很难同时显示多个小行星和它们轻松互动。

我建议你:

  • 使 Asteroid 成为非组件逻辑 class,
    • 一个知道如何通过给它一个public void draw(Graphics2D g2)方法来画自己的人
    • 知道如何根据游戏循环的滴答移动自己的人
  • 创建一个 JPanel 仅用于绘制整个动画
  • 给这个 JPanel 一个 Asteroid 对象的集合,比如在 ArrayList 中
  • 在此 JPanel 的 paintComponent 中,遍历集合中的所有小行星,调用每个小行星的 draw(...) 方法
  • 使用 Swing 定时器以 Swing 线程安全且可控的方式驱动整个动画。在这个定时器的actionPerformed中,告诉每颗小行星移动,然后在绘图JPanel
  • 上调用repaint()
  • 不要在循环内调用.repaint(),而是在循环结束后调用
  • 根据您的 Shapes 创建一个小的 BufferedImage 精灵,并将其绘制为小行星

一个简单的例子来说明我的意思:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;

@SuppressWarnings("serial")
public class Game2 extends JPanel {
    private static final int PREF_W = 1000;
    private static final int PREF_H = 800;
    private static final int TIMER_DELAY = 20;
    private List<Asteroid2> asteroids = new ArrayList<>();

    public Game2() {
        setBackground(Color.BLACK);
        int rows = 5;
        int cols = 5;
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                Asteroid2 asteroid = new Asteroid2();
                asteroid.setX(j * (PREF_W / cols));
                asteroid.setY(i * (PREF_H / rows));
                asteroids.add(asteroid);
            }
        }
        new Timer(TIMER_DELAY, e -> {
            for (Asteroid2 asteroid2 : asteroids) {
                asteroid2.move();
            }
            repaint();
        }).start();
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        for (Asteroid2 asteroid : asteroids) {
            asteroid.draw(g);
        }
    }

    @Override
    public Dimension getPreferredSize() {
        if (isPreferredSizeSet()) {
            return super.getPreferredSize();
        }
        return new Dimension(PREF_W, PREF_H);
    }

    private static void createAndShowGui() {
        Game2 mainPanel = new Game2();

        JFrame frame = new JFrame("Game2");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(mainPanel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }
}
class Asteroid2 {
    private static final int[] POLY_X = { 20, 40, 60, 55, 60, 24, 4, 0 };
    private static final int[] POLY_Y = { 0, 7, 20, 40, 80, 70, 40, 0 };
    private static final Color ASTEROID_COLOR = Color.decode("#52575D");
    private Image image;
    private int x;
    private int y;

    public Asteroid2() {
        Polygon poly = new Polygon(POLY_X, POLY_Y, POLY_X.length);
        image = new BufferedImage(60, 80, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2 = (Graphics2D) image.getGraphics();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setColor(ASTEROID_COLOR);
        g2.fill(poly);
        g2.dispose();
    }

    public void move() {
        x++;
        y++;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }

    public void draw(Graphics g) {
        if (image != null) {
            g.drawImage(image, x - 20, y, null);
        }
    }
}