3

我想通过按钮的 ActionListenermain()的方法使“主线程”(运行的线程启动)做一些工作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();
}

但我发现这个解决方案非常不雅。

我已经阅读了Swing 中的并发课程,但我仍然感到困惑(我应该实现一个工作线程——这不是有点矫枉过正吗?)。我还没有完成任何实际的多线程工作,所以我有点迷茫。

有没有办法告诉主线程(它将无限期地休眠,等待用户操作)“好的,现在醒来并执行此操作(显示游戏面板并开始游戏)”?

谢谢你的帮助。

编辑: 为了清楚起见,这就是我的游戏循环的样子:

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) {}
}
4

5 回答 5

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);
   }
}
于 2012-04-21T22:10:32.663 回答
1

您应该使用SwingWorker这将doInBackground()在后台线程中执行代码,并在停止done()后执行 EDT 中的代码doInBackground()

于 2012-04-21T22:15:25.310 回答
0

最简单的方法:使用CountDownLatch. 您将其设置为 1,以任何适当的方式使其在 Swing 代码中可用,并在主线程中为您提供await

于 2012-04-21T22:06:34.940 回答
0

您可以考虑使用 SwingUtilities.invokeAndWait() 显示带有游戏面板的模式对话框,以便在关闭对话框时控件返回主线程。

于 2012-04-21T22:15:03.953 回答
0

您可以使除 EDT 之外的所有代码在单线程执行服务上运行,然后在需要执行某些代码时发布可运行文件。

于 2012-04-21T22:20:40.507 回答