-1

我正在尝试用 Java 制作 2D 游戏,但是当我在线程中调用 repaint() 方法时,会出现一个奇怪的仅灰色窗口。

这是我到目前为止的源代码:

  1. 太空射手.java

    package spaceshooter;
    
    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.KeyEvent;
    import java.awt.event.KeyListener;
    public class Spaceshooter extends JFrame implements KeyListener, Runnable {
    
    private Player player = new Player(5, 186, this);
    private boolean up, down;
    
    public Spaceshooter(String title) {
        super(title);
        this.setFocusable(true);
        this.addKeyListener(this);
    }
    
    @Override
    public void paint(Graphics gr) {
        super.paint(gr);
    
        gr.setColor(Color.BLACK);
        gr.fillRect(0, 0, 800, 500);
    
        player.paintPlayer(gr);
    }
    
    public static void main(String[] args) {
        Spaceshooter shooter = new Spaceshooter("Spaceshooter");
        new Thread(shooter).start();
        shooter.setSize(800,500);
        shooter.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        shooter.setVisible(true);
    }
    
    @Override
    public void keyTyped(KeyEvent e) {
    }
    
    @Override
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == 38) {
            up = true;
            down = false;
        } else if (e.getKeyCode() == 40) {
            down = true;
            up = false;
        }
    }
    
    @Override
    public void keyReleased(KeyEvent e) {
        down = false;
        up = false;
    }
    
    @Override
    public void run() {
        while(true) {
            if (up) {
                player.moveUp();
            } else if (down) {
                player.moveDown();
            }
            repaint();
        try {
            Thread.sleep(20);
        } catch (InterruptedException ex) {
            Logger.getLogger(Spaceshooter.class.getName()).log(Level.SEVERE, null, ex);
        }
        }
    }
    }
    
  2. 播放器.java

     package spaceshooter;
    
     import java.awt.Component;
     import java.awt.Graphics;
     import java.awt.Toolkit;
    
     public class Player {
    
    private int x, y;
    private Component comp;
    
    public Player(int x, int y, Component comp) {
        this.x = x;
        this.y = y;
        this.comp = comp;
    }
    
    public void moveUp() {
        y -= 5;
    }
    
    public void moveDown() {
        y += 5;
    }
    
    public void paintPlayer(Graphics gr) {
        gr.drawImage(Toolkit.getDefaultToolkit().getImage("images/player.png"), x, y, comp);
    }
    
    }
    

提前感谢您的回答!

4

1 回答 1

2

什么是 EDT?

Swing 事件处理代码在称为事件分派线程的特殊线程上运行。大多数调用 Swing 方法的代码也在这个线程上运行。这是必要的,因为大多数 Swing 对象方法都不是“线程安全的”。所有与 GUI 相关的任务,应该对 GUI 进行任何更新,而绘制过程必须在 EDT 上进行,这涉及将请求包装在一个事件中并将其处理到EventQueue. 然后事件从同一个队列中一个一个地分派,按照它们排队的顺序,先入先出。也就是说,如果就是,如果 被排到了之前Event A的队列中,那么事件B将不会在事件A之前被调度。EventQueueEvent B

您执行的任何可能需要一段时间的任务,可能会阻塞 EDT,不会发生调度,不会进行更新,因此您的应用程序会冻结。你必须杀死它才能摆脱这种冻结状态。

在您的程序中,除了创建您的JFrame并使其在主线程中可见(我们也不应该这样做):

    while(true) {
        if (up) {
            player.moveUp();
        } else if (down) {
            player.moveDown();
        }
        repaint();
    try {
        Thread.sleep(20);
    } catch (InterruptedException ex) {
        Logger.getLogger(Spaceshooter.class.getName()).log(Level.SEVERE, null, ex);
    }
    }

您正在从您发送的线程中发布repaint()请求,然后立即进入睡眠状态。

SwingUtilities.invokeLater():

Swing 提供了一个很好的功能来向 EDTSwingUtilities.invokeLater(new Runnable(){})发布repaint请求。你所要做的就是写:

    SwingUtilities.invokeLater(new Runnable()
       {
         public void run()
         {
               repaint();
         }
   });

现在还有一点要提:

  1. 我们不应该使用Runnable. 创建另一个类实现Runnable,在其中进行计算,然后用于SwingUtilities发布组件更新请求。
  2. 我们不应该直接在上面进行定制绘画JFrame。JFrame 是一个顶级组件。它更像是一个包含整个应用程序的容器。如果您想要自定义绘画,请使用自定义组件MyCanvas extends JComponent
  3. 我们不应该覆盖paint()函数。相反paintComponent(g),它将很好地服务于我们的自定义绘画目的。
  4. 学习使用 Swing Timer 类进行及时重复的 GUI 渲染任务。

教程资源和参考:

  1. 事件调度线程
  2. 事件队列
  3. 如何使用摆动计时器
  4. 课程:执行自定义绘画
于 2013-10-27T23:54:05.930 回答