【问题标题】:KeyListener method keyPressed not workingKeyListener 方法 keyPressed 不起作用
【发布时间】:2020-04-25 05:18:38
【问题描述】:

我正在尝试使用 java swing 制作一个小游戏,并且已经走得很远,但是现在我的 KeyListener 没有使用 keyPressed 方法。

这是我的一些代码

public class Screen extends JPanel implements Runnable{

private static final int WIDTH = 300, HEIGHT = 300, RIGHT = 0, LEFT = 1, UP = 2, DOWN = 3, STILL = 4;
private Thread thread;
private boolean running = false;
private int direction = DOWN;

   public Screen() {
    setFocusable(true);
    addKeyListener(new Key());
    setPreferredSize(new Dimension(WIDTH, HEIGHT));
    start();
   }

   public void tick(){
     System.out.println(direction)  
   }

   public void start() {
    running = true;
    thread = new Thread(this, "Game Loop");
    thread.start();
}

   public void run() {
     while (running) {
       tick();
       repaint();
    }
}

   private class Key implements KeyListener{
    @Override
    public void keyTyped(KeyEvent e) {
        // TODO Auto-generated method stub

    }

    @Override
    public void keyPressed(KeyEvent e) {
        int key = e.getKeyCode();

        if (key == KeyEvent.VK_D) {
            direction = RIGHT;
        }

        if (key == KeyEvent.VK_A) {
            direction = LEFT;
        }

        if (key == KeyEvent.VK_W) {
            direction = UP;
        }

        if (key == KeyEvent.VK_S) {
            direction = DOWN;
        }

    }

    @Override
    public void keyReleased(KeyEvent e) {
        // TODO Auto-generated method stub

    }
   }
}

我正在查看控制台并期望输出从 3(即 DOWN)更改为我按下的任何按钮,但它永远不会。一段时间后,我意识到 keyPressed 方法有问题,我只是不知道是什么。

【问题讨论】:

  • 请改用key bindings API - 它可以解决问题,而无需大量破解。另外,请注意,Swing 不是线程安全的 - 因此从 EDT 的上下文之外修改 UI 的状态可能会导致绘制问题
  • 见下面的例子

标签: java swing keylistener


【解决方案1】:

您不需要单独的“游戏循环”线程。 Swing 是事件驱动的。当用户按下计算机键盘上的键时,将调用您的按键侦听器代码。

正如MadProgrammer在他的comment to your question中提到的

从 EDT 上下文之外修改 UI 的状态可能会导致绘制问题

换句话说,Swingsingle threaded,并且该线程被称为 Event Dispatch Thread (EDT),所有更改 GUI 的代码都应该是仅在 EDT 上执行。

以下代码是一个独立的Swing 应用程序,它显示一个可聚焦的JPanel。当您按下相关键盘“方向”键之一时,即 A 或 D 或 S 或 W,控制台会显示方向。按下任何其他键都会导致方向仍被写入控制台。请注意,我使用enum 表示各个方向,而不是整数常量。

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;

public class Screen01 extends JPanel implements Runnable {
    public enum Directions {RIGHT, LEFT, UP, DOWN, STILL};

    private static final int WIDTH = 300, HEIGHT = 300;
    private Directions direction;

    public Screen01() {
        setPreferredSize(new Dimension(WIDTH, HEIGHT));
        setFocusable(true);
        addKeyListener(new Key());
    }

    public void run() {
        createAndDisplayGui();
    }

    private void createAndDisplayGui() {
        JFrame frame = new JFrame("Screen");
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.add(this, BorderLayout.CENTER);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    private class Key implements KeyListener {

        @Override
        public void keyTyped(KeyEvent e) {
            // Do nothing.
        }

        @Override
        public void keyPressed(KeyEvent e) {
            int key = e.getKeyCode();
            switch (key) {
                case KeyEvent.VK_D:
                    direction = Directions.RIGHT;
                    break;
                case KeyEvent.VK_A:
                    direction = Directions.LEFT;
                    break;
                case KeyEvent.VK_W:
                    direction = Directions.UP;
                    break;
                case KeyEvent.VK_S:
                    direction = Directions.DOWN;
                    break;
                default:
                    direction = Directions.STILL;
            }
            System.out.println("Direction = " + direction);
        }

        @Override
        public void keyReleased(KeyEvent e) {
            // Do nothing.
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Screen01());
    }
}

【讨论】:

  • 感谢您的意见。我将如何更改 Thread 的代码?
  • @MatthewTapia 你指的是什么线程?如果您指的是 Game Loop 线程,那么正如我在回答的第一句话中所写,您不需要它。你认为你确实需要它吗?如果你认为你有,那么你能解释一下为什么你认为你必须有 Game Loop 线程吗?
【解决方案2】:

利用key bindings API,它会解决这个问题,以及所有其他与KeyListener 相关的问题,而不会因为它“有时”起作用而费尽心思。

Swing 是单线程的。这意味着您应该小心从事件调度线程的上下文之外(直接或间接)更新 UI 的状态,这还包括 UI 可能依赖的任何状态。

查看Concurrency in Swing了解更多详情。

如果没有更多细节,最简单的解决方案之一是使用 Swing Timer 作为主要的“tick”动作。每次它“打勾”时,您都会检查输入的状态并更新模型的状态并触发重绘以更新 UI

例如...

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;

public class Main {

    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public static class TestPane extends JPanel {

        enum Input {
            UP, DOWN, LEFT, RIGHT
        }

        private Set<Input> input = new HashSet<>();

        public TestPane() {
            InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap am = getActionMap();

            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Left.pressed");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Left.released");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Right.pressed");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Right.released");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "Up.pressed");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Up.released");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "Down.pressed");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Down.released");

            am.put("Left.pressed", new InputAction(Input.LEFT, input, false));
            am.put("Left.released", new InputAction(Input.LEFT, input, true));
            am.put("Right.pressed", new InputAction(Input.RIGHT, input, false));
            am.put("Right.released", new InputAction(Input.RIGHT, input, true));
            am.put("Down.pressed", new InputAction(Input.DOWN, input, false));
            am.put("Down.released", new InputAction(Input.DOWN, input, true));
            am.put("Up.pressed", new InputAction(Input.UP, input, false));
            am.put("Up.released", new InputAction(Input.UP, input, true));

            input.add(Input.UP);
            input.add(Input.DOWN);
            input.add(Input.LEFT);
            input.add(Input.RIGHT);

            Timer timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    // Check what's currently been pressed
                    // and update the state accordingly...
                    if (input.contains(Input.UP)) {
                        //..
                    }
                    if (input.contains(Input.DOWN)) {
                        //..
                    }
                    if (input.contains(Input.LEFT)) {
                        //..
                    }
                    if (input.contains(Input.RIGHT)) {
                        //..
                    }
                    repaint();
                }
            });
            timer.start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();

            int midX = getWidth() / 2;
            int midY = getHeight() / 2;

            FontMetrics fm = g2d.getFontMetrics();
            int spaceWidth = fm.stringWidth("M");
            int spaceHeight = fm.getHeight();

            if (input.contains(Input.UP)) {
                String text = "UP";
                g2d.drawString(text,
                        midX - (fm.stringWidth(text)) / 2,
                        (midY - (spaceHeight * 2) + fm.getAscent()));
            }
            if (input.contains(Input.DOWN)) {
                String text = "DOWN";
                g2d.drawString(text,
                        midX - (fm.stringWidth(text)) / 2,
                        (midY + spaceHeight + fm.getAscent()));
            }
            if (input.contains(Input.LEFT)) {
                String text = "LEFT";
                g2d.drawString(text,
                        (midX - spaceWidth - fm.stringWidth(text)),
                        (midY + (fm.getAscent() / 2)));
            }
            if (input.contains(Input.RIGHT)) {
                String text = "RIGHT";
                g2d.drawString(text,
                        (midX + spaceWidth),
                        (midY + (fm.getAscent() / 2)));
            }
            g2d.dispose();
        }

        public class InputAction extends AbstractAction {

            private Input input;
            private Set<Input> inputSet;
            private boolean onRelease;

            public InputAction(Input input, Set<Input> inputSet, boolean onRelease) {
                this.input = input;
                this.inputSet = inputSet;
                this.onRelease = onRelease;
            }

            @Override
            public void actionPerformed(ActionEvent e) {
                if (onRelease) {
                    inputSet.remove(input);
                } else {
                    inputSet.add(input);
                }
            }

        }

    }
}

这是一个基本示例,直接在paintComponent 方法中打印键输入的状态。更现实的实现是使用TimerActionListener 来检查input 状态并相应地更改所需的模型,这在示例中有所暗示。

如果您需要更直接的控制,那么您需要避免使用 Swing 绘画系统并自行控制。为此,您需要使用BufferStrategy。这是一个更复杂的解决方案,但它也更强大,因为您可以完全控制 UI 的更新时间

【讨论】:

    猜你喜欢
    • 2016-04-15
    • 2011-07-09
    • 2015-11-09
    • 2011-10-27
    • 2018-06-24
    • 2014-04-09
    • 1970-01-01
    • 2015-07-12
    • 1970-01-01
    相关资源
    最近更新 更多