我想让“主线程”(运行main()
的线程启动)从按钮的ActionListener的actionPerformed()
方法做一些工作,但我不知道如何实现这一点
更多背景信息:
我目前正在使用Swing(一种俄罗斯方块的风格)编写2D游戏 当应用程序启动时,会打开一个窗口,显示游戏的主菜单。 向用户展示了几种可能性,其中之一是通过按下“开始”按钮来启动游戏,这将导致游戏面板显示并触发游戏的主循环。
为了能够在两个面板(主菜单和游戏面板)之间切换,我使用的是CardLayout管理器,然后我可以通过调用show()
来显示一个面板。
我的想法是,我希望我的开始按钮有一个看起来像这样的监听器:
public class StartListener implements ActionListener {
StartListener() {}
public void actionPerformed(ActionEvent e) {
displayGamePanel();
startGame();
}
}
但是这不起作用,因为从事件派发线程调用了actionPerformed()
,所以调用startGame()
(触发主循环:游戏逻辑更新+ repaint()
调用每一帧)阻止整个线程。
我现在处理此问题的方法是actionPerformed()
只更改布尔标志值:public void actionPerformed(ActionEvent e) {
startPushed = true;
}
然后最终由主线程检查:
while (true) {
while (!g.startPushed) {
try {
Thread.sleep(100);
} catch (Exception e) {}
}
g.startPushed = false;
g.startGame();
}
但我觉得这个解决方案非常不优雅。
我已经阅读了Concurrency in Swing课程,但我仍然感到困惑(我应该实现一个工作线程 - 是不是有点矫枉过正?)。我还没有做过任何实际的多线程工作,所以我有点失落。
是不是有办法告诉主线程(哪个会无限期地睡觉,等待用户操作)“好的,现在醒来并执行此操作(显示游戏面板并开始游戏)”?< / p>
感谢您的帮助。
修改 为了清楚起见,这就是我的游戏循环:
long lastLoopTime = System.currentTimeMillis();
long dTime;
int delay = 10;
while (running) {
// compute the time that has gone since the last frame
dTime = System.currentTimeMillis() - lastLoopTime;
lastLoopTime = System.currentTimeMillis();
// UPDATE STATE
updateState(dTime);
//...
// UPDATE GRAPHICS
// thread-safe: repaint() will run on the EDT
frame.repaint()
// Pause for a bit
try {
Thread.sleep(delay);
} catch (Exception e) {}
}
答案 0 :(得分:4)
这没有意义:
但是这不起作用,因为从事件派发线程调用了actionPerformed(),所以对startGame()的调用(触发主循环:每帧的游戏逻辑更新+ repaint()调用)会阻止整个线程。
由于你的游戏循环不应该阻止EDT。您是否在游戏循环中使用Swing Timer或后台线程?如果没有,请这样做。
关于:
while (true) {
while (!g.startPushed) {
try {
Thread.sleep(100);
} catch (Exception e) {}
}
g.startPushed = false;
g.startGame();
}
也不要这样做,而是使用侦听器来做这种事情。
如,
import java.awt.CardLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class GameState extends JPanel {
private CardLayout cardlayout = new CardLayout();
private GamePanel gamePanel = new GamePanel();
private StartPanel startpanel = new StartPanel(this, gamePanel);
public GameState() {
setLayout(cardlayout);
add(startpanel, StartPanel.DISPLAY_STRING);
add(gamePanel, GamePanel.DISPLAY_STRING);
}
public void showComponent(String displayString) {
cardlayout.show(this, displayString);
}
private static void createAndShowGui() {
GameState mainPanel = new GameState();
JFrame frame = new JFrame("GameState");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class StartPanel extends JPanel {
public static final String DISPLAY_STRING = "Start Panel";
public StartPanel(final GameState gameState, final GamePanel gamePanel) {
add(new JButton(new AbstractAction("Start") {
@Override
public void actionPerformed(ActionEvent e) {
gameState.showComponent(GamePanel.DISPLAY_STRING);
gamePanel.startAnimation();
}
}));
}
}
class GamePanel extends JPanel {
public static final String DISPLAY_STRING = "Game Panel";
private static final int PREF_W = 500;
private static final int PREF_H = 400;
private static final int RECT_WIDTH = 10;
private int x;
private int y;
public void startAnimation() {
x = 0;
y = 0;
int timerDelay = 10;
new Timer(timerDelay , new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
x++;
y++;
repaint();
}
}).start();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.fillRect(x, y, RECT_WIDTH, RECT_WIDTH);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
}
答案 1 :(得分:1)
您应该使用SwingWorker这将在后台线程中执行doInBackground()
中的代码,并在done()
停止后执行EDT中doInBackground()
中的代码
答案 2 :(得分:0)
最简单的方法:使用CountDownLatch
。您将其设置为1,通过任何适当的方式使其在Swing代码中可用,并在主线程中await
。
答案 3 :(得分:0)
您可以考虑使用SwingUtilities.invokeAndWait()显示游戏面板的模态对话框,以便在关闭对话框时控件返回主线程。
答案 4 :(得分:0)
您可以使除EDT之外的所有代码在单线程执行服务上运行,然后只需在需要执行某些代码时发布runnables。