【问题标题】:KeyListener in a new thread for game input capture用于游戏输入捕获的新线程中的 KeyListener
【发布时间】:2017-10-29 21:17:12
【问题描述】:

我正在使用 Swing 用 Ja​​va 制作一个老式的 Snake 游戏。我已经读过,为了实时捕获输入,我需要在新线程中运行我的游戏循环,这样 wait() 方法就不会干扰输入捕获。我已经让 InputCapture 类实现了 KeyListener 并且我已经实现了 keyPressed() 这样的方法:

public class InputCapture implements KeyListener {

    private Direction capturedDirection;

    //Methods
    @Override
    public void keyPressed(KeyEvent e) {
        boolean inputConsoleDebug = true;
        if (e.getKeyCode() == KeyEvent.VK_LEFT) {
            capturedDirection = Direction.left;
            if (inputConsoleDebug) System.out.println("LEFT");
        } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
            capturedDirection = Direction.right;
            if (inputConsoleDebug) System.out.println("RIGHT");
        } else if (e.getKeyCode() == KeyEvent.VK_UP) {
            capturedDirection = Direction.up;
            if (inputConsoleDebug) System.out.println("UP");
        } else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
            capturedDirection = Direction.down;
            if (inputConsoleDebug) System.out.println("DOWN");
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {
    }

    @Override
    public void keyTyped(KeyEvent e) {
    }

    public Direction getCapturedDirection() {
        return capturedDirection;
    }
}

然后我创建了 Game 类扩展 Thread 并将游戏循环代码放入 run() 方法中:

public class Game extends Thread {

    private Board board;
    private Snake snake;
    private JFrame frame;
    private long waitTime;
    private int difficultyStep;
    private Direction inputDirection;
    private InputCapture inputManager;

    //Constructors
    Game(Dimension boardSize) {
        //Set difficulty
        int applesToWin = boardSize.width * boardSize.height - 1;
        final int easiestWaitTime = 1000;
        final int hardestWaitTime = 100;
        difficultyStep = (easiestWaitTime - hardestWaitTime) / applesToWin;
        waitTime = easiestWaitTime;
        //Set starting point
        final int startingPointX = boardSize.width / 2;
        final int startingPointy = boardSize.height / 2;
        //Set board and snake
        board = new Board(boardSize);
        snake = new Snake(board, startingPointX, startingPointy);
        //Set window Frame
        frame = new JFrame(SnakeApplication.getApplicationName());
        frame.setContentPane(board);
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.pack();
        frame.setResizable(false);
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                super.windowClosing(e);
                interrupt();
            }
        });
        //Set input manager
        inputManager = new InputCapture();
        frame.addKeyListener(inputManager);
        inputDirection = null;
    }

    //Methods
    public void run() {
        board.spawnApple();
        while (!isWon()) {
            try {
                sleep(waitTime);
            } catch (InterruptedException e) {
                return;
            }
            try {
                inputDirection = inputManager.getCapturedDirection();
                snake.move(inputDirection);
            } catch (LosingMove e) {
                showGameOverDialog();
                return;
            }
            board.repaint();
        }
        showWinDialog();
    }

    JFrame getFrame() {
        return frame;
    }

    private boolean isWon() {
        for (int row = 0; row < board.getFields().length; row++) {
            for (int col = 0; col < board.getFields()[0].length; col++) {
                if (!(board.getFields()[row][col].getContent() instanceof Snake.SnakeNode)) return false;
            }
        }
        return true;
    }

    private void showGameOverDialog() {
        JFrame gameOverFrame = new JFrame();
        JOptionPane.showMessageDialog(gameOverFrame, "Game Over!");
    }

    private void showWinDialog() {
        JFrame gameOverFrame = new JFrame();
        JOptionPane.showMessageDialog(gameOverFrame, "You Win!");
    }
}

在我的MainMenu 类中,我创建了startNewGame() 方法,该方法在单击新游戏按钮时被调用。该方法创建Game对象并通过调用start()方法启动一个新线程。

public class MainMenu {

    //Form components references
    private JButton exitButton;
    private JFrame frame;
    private JPanel mainPanel;
    private JButton newGameButton;
    private JLabel titleLabel;

    //Constructors
    MainMenu() {
        //Set window Frame
        frame = new JFrame(SnakeApplication.getApplicationName());
        frame.setContentPane(mainPanel);
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setResizable(false);
        frame.pack();
        newGameButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                startNewGame();
            }
        });
        exitButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                exitGame();
            }
        });
    }

    JFrame getFrame() {
        return frame;
    }

    private Dimension showBoardSizeDialog() {
        Frame boardSizeFrame = new Frame();
        int width = Integer.parseInt(JOptionPane.showInputDialog(boardSizeFrame, "Set board's width:"));
        int height = Integer.parseInt(JOptionPane.showInputDialog(boardSizeFrame, "Set board's height:"));
        return new Dimension(width, height);
    }

    private void startNewGame() {
        Dimension boardSize = showBoardSizeDialog();
        frame.setVisible(false);
        Game game = new Game(boardSize);
        game.getFrame().setVisible(true);
        //Starting game loop in a new thread
        game.start();
        try {
            game.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        frame.setVisible(true);
    }
}

但在测试应用时,它会卡在游戏循环中,根本无法捕获输入。为什么?我试图调试它,但是每次启动新线程时,它都会卡在游戏循环中。 Board 本身仅在主线程结束执行时才被绘制。为什么?如果执行被卡在那里,不应该在游戏循环期间重新绘制多次吗?

此外,当单击框架的关闭按钮(红色 X 按钮)时,我已经使线程中断,因此执行可以返回到 MainMenu 并重新出现,但单击红色关闭按钮无效。

【问题讨论】:

标签: java multithreading swing input keylistener


【解决方案1】:

由于在startNewGame 中调用game.join(),程序冻结。 join 阻止它被 from 调用的线程继续执行,直到它被 on 调用的线程终止。在您的情况下,join 违背了使用另一个线程的目的,因此您应该删除它。

不过,还有其他问题。您可能不应该使用线程。 You should probably use a Swing TimerSwing isn't thread-safe,我已经可以看到一些你的代码也不是线程安全的地方。 (例如,您需要将 capturedDirection 声明为 volatile。)使用 Swing 编写正确的多线程代码有点复杂,而仅使用计时器会简单得多。

否则,如果您不使用计时器,则需要使用例如游戏线程(写入共享游戏状态)和绘制(可能从共享游戏状态读取)的 Swing 线程之间的同步。否则,您可能会遇到难以诊断的问题。

另见The Use of Multiple JFrames: Good or Bad Practice?

【讨论】:

    【解决方案2】:

    你应该让你的 Game 类扩展 Runnable 而不是 Thread

    然后将游戏放在不同的线程中:

    Game theGame = ... // initialization code here
    new Thread(theGame).start();
    

    【讨论】:

    • 使Game 实施Runnable 没有帮助。它仍然卡住,关闭按钮仍然没有响应。
    猜你喜欢
    • 2012-07-09
    • 1970-01-01
    • 1970-01-01
    • 2020-11-11
    • 1970-01-01
    • 2019-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多