问题
I'm trying to make a 2D game in Java, but when I call the repaint() method in a thread there's an odd grey-only window.
Here's the source code I have so far:
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); } } } }
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); } }
Thanks for your answers in advance!
回答1:
What is EDT ?
Swing event handling code runs on a special thread known as the event dispatch thread. Most code that invokes Swing methods also runs on this thread. This is necessary because most Swing object methods are not "thread safe". All GUI related task, any update should be made to GUI while painting process must happen on the EDT, which involves wrapping the request in an event and processing it onto the EventQueue
. Then the event are dispatched from the same queue in the one by one in order they en-queued, FIRST IN FIRST OUT. That is, if That is, if Event A
is enqueued to the EventQueue
before Event B
then event B will not be dispatched before event A.
Any task you perform which may take a while, likely to block the EDT, no dispatching will happen, no update will be made and hence your application FREEZES. You will have to kill it to get rid of this freezing state.
In your program, aside from creating your JFrame
and making it to visible from the main thread (which we also should not do):
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);
}
}
you are posting repaint()
request from the thread which you sent to sleep right immediately after that.
SwingUtilities.invokeLater():
Swing provides a nice function SwingUtilities.invokeLater(new Runnable(){})
for posting repaint
request to the EDT. All you have to do is to write:
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
repaint();
}
});
Now some more thing to mention:
- We should not implements a GUI component with
Runnable
. Make another class implementingRunnable
, make your computation inside that then useSwingUtilities
to post the component update request. - we should not made custom painting on
JFrame
directly. JFrame is a top level component. It is more like a container which contains your whole app. If you want custom painting use a custom componentMyCanvas extends JComponent
. - we should not override
paint()
function. InsteadpaintComponent(g)
will serve our custom painting purposes nicely. - Learn to use Swing Timer class for timely repeated GUI rendering task.
Tutorial Resource and References:
- The Event Dispatch Thread
- EventQueue
- How to Use Swing Timers
- Lesson: Performing Custom Painting
来源:https://stackoverflow.com/questions/19620749/java-2d-game-repaint-makes-window-grey