Java 2D Game:repaint(); 使窗口变灰

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

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

  1. Spaceshooter.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. Player.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); } } 

感谢您提前的答案!

什么是EDT?

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

您执行的任何可能需要一段时间的任务,可能会阻止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提供了一个很好的函数SwingUtilities.invokeLater(new Runnable(){})用于将repaint请求发布到EDT。 你所要做的就是写:

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

现在还要提一下:

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

教程资源和参考:

  1. 事件派遣线程
  2. 的EventQueue
  3. 如何使用Swing Timers
  4. 课程:执行自定义绘画