您的主要问题与KeyListener 在您尝试使用它的应用程序中不可靠有关。 KeyListener 要求它注册的组件能够接收键盘焦点并且在触发按键事件之前具有键盘焦点。焦点很容易被其他组件窃取。
对您的问题最可靠的解决方案是使用key bindings API,它部分地是为了解决这个问题而开发的。
您可能还想阅读How to Use Actions 以了解这部分 API 的工作原理
因此,改编 Trying to move JLabels on a JPanel with GridLayout 中的代码,这是最简单的示例,与您似乎正在尝试做的事情最接近,您最终可能会得到类似...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JLabel player;
public TestPane() {
player = makeLabel("P");
player.setSize(player.getPreferredSize());
add(player);
addKeyBinding("left", KeyEvent.VK_LEFT, new MoveAction(player, -4, 0));
addKeyBinding("right", KeyEvent.VK_RIGHT, new MoveAction(player, 4, 0));
addKeyBinding("up", KeyEvent.VK_UP, new MoveAction(player, 0, -4));
addKeyBinding("down", KeyEvent.VK_DOWN, new MoveAction(player, 0, 4));
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected void addKeyBinding(String name, int keyCode, Action action) {
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(KeyStroke.getKeyStroke(keyCode, 0), name);
actionMap.put(name, action);
}
protected JLabel makeLabel(String text) {
JLabel label = new JLabel(text);
label.setBorder(new CompoundBorder(
new LineBorder(Color.GRAY),
new EmptyBorder(4, 4, 4, 4)));
return label;
}
public class MoveAction extends AbstractAction {
private final int xDelta, yDelta;
private final JComponent component;
public MoveAction(JComponent component, int xDelta, int yDelta) {
this.component = component;
this.xDelta = xDelta;
this.yDelta = yDelta;
}
@Override
public void actionPerformed(ActionEvent e) {
Point location = component.getLocation();
location.x += xDelta;
location.y += yDelta;
component.setLocation(location);
repaint();
}
}
}
}
但是多个同时击键呢?
嗯,这不是一个独特的问题,最常见的解决方法是使用一系列标志来确定当前是否按下了某个键。然后,您可以使用这些标志来确定如何最好地移动对象
因此,根据第一个示例进行调整,您最终可能会得到类似...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.Set;
import java.util.TreeSet;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
public class Test2 {
public static void main(String[] args) {
new Test2();
}
public Test2() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public enum Direction {
UP, DOWN, LEFT, RIGHT;
}
public class Controller {
private Set<Direction> directions;
private JComponent player;
public Controller(JComponent player) {
this.player = player;
directions = new TreeSet<>();
}
public void released(Direction direction) {
directions.remove(direction);
updatePosition();
}
public void pressed(Direction direction) {
directions.add(direction);
updatePosition();
}
protected void updatePosition() {
Point location = player.getLocation();
if (directions.contains(Direction.UP)) {
location.y -= 4;
}
if (directions.contains(Direction.DOWN)) {
location.y += 4;
}
if (directions.contains(Direction.LEFT)) {
location.x -= 4;
}
if (directions.contains(Direction.RIGHT)) {
location.x += 4;
}
player.setLocation(location);
}
}
public class TestPane extends JPanel {
private JLabel player;
public TestPane() {
player = makeLabel("P");
player.setSize(player.getPreferredSize());
add(player);
Controller controller = new Controller(player);
addKeyBinding("left", KeyEvent.VK_LEFT, Direction.LEFT, controller);
addKeyBinding("right", KeyEvent.VK_RIGHT, Direction.RIGHT, controller);
addKeyBinding("up", KeyEvent.VK_UP, Direction.UP, controller);
addKeyBinding("down", KeyEvent.VK_DOWN, Direction.DOWN, controller);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected void addKeyBinding(String name, int keyCode, Direction direction, Controller controller) {
InputMap inputMap = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(KeyStroke.getKeyStroke(keyCode, 0, true), name + "-released");
inputMap.put(KeyStroke.getKeyStroke(keyCode, 0, false), name + "-pressed");
actionMap.put(name + "-released", new MoveAction(direction, controller, true));
actionMap.put(name + "-pressed", new MoveAction(direction, controller, false));
}
protected JLabel makeLabel(String text) {
JLabel label = new JLabel(text);
label.setBorder(new CompoundBorder(
new LineBorder(Color.GRAY),
new EmptyBorder(4, 4, 4, 4)));
return label;
}
public class MoveAction extends AbstractAction {
private Direction direction;
private Controller controller;
private boolean released;
public MoveAction(Direction direction, Controller controller, boolean released) {
this.direction = direction;
this.controller = controller;
this.released = released;
}
@Override
public void actionPerformed(ActionEvent e) {
if (released) {
controller.released(direction);
} else {
controller.pressed(direction);
}
}
}
}
}
下一次,尝试使用一些代码而不是强迫它们重组并提出使用这个键绑定的建议。如果你觉得按键绑定这么厉害,不如用按键绑定来修改我的代码,看看它有多复杂
叹息 - 因为我不应该这样做,即使用你自己的话来说,你也不想要“复制和粘贴”的答案,但是,显然这就是你想要的......
import javax.swing.JFrame;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.beans.PropertyChangeListener;
// I'd prefer to extend from JPanel, as it provides bases for
// self contained responsibility, but apparently, that's too "advanced"
public class PlayervsPlayer implements ActionListener {
private JFrame window;
private Timer timer;
private int win_size;
private int ship_clearence = 45;
private int speed = 3;
private final int stop1 = 0;
private final int stop2 = 1;
private final int left1 = 2;
private final int left2 = 3;
private final int right1 = 4;
private final int right2 = 5;
private int dir1 = left1;
private int dir2 = right2;
// Sprite background;
JLabel ship1 = new JLabel("Spaceship.png");
JLabel ship2 = new JLabel("Spaceship2.png");
public PlayervsPlayer(JFrame w, int win_s) {
window = w;
// background = backdrop;
win_size = win_s;
JComponent contenPane = (JComponent) w.getContentPane();
InputMap inputMap = contenPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = contenPane.getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "player1-left");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "player1-right");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0), "player2-left");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_E, 0), "player2-right");
// I'd prefer a single "Move Action" class which could
// make these updates, but that might be "too advanced"
actionMap.put("player1-left", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Player 1 - left");
dir1 = left1;
}
});
actionMap.put("player1-right", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Player 1 - right");
dir1 = right1;
}
});
actionMap.put("player2-left", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Player 2 - left");
dir2 = left2;
}
});
actionMap.put("player2-right", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Player 2 - right");
dir2 = right2;
}
});
}
public void pvpmain() {
System.out.println("Player vs Player working!");
ship1.setSize(125, 125);
ship1.setLocation((int) ((win_size / 2) - (ship1.getWidth() / 2)), win_size - ship1.getHeight() - ship_clearence);
ship2.setSize(125, 125);
ship2.setLocation((int) ((win_size / 2) - (ship1.getWidth() / 2)), 0);
window.add(ship1, 0);
window.add(ship2, 0);
window.repaint();
timer = new Timer(10, this);
timer.start();
}
public void move() {
if (dir1 == left1) {
if (ship1.getX() <= speed) {
dir1 = stop1;
} else {
ship1.setLocation(ship1.getX() - speed, ship1.getY());
}
} else if (dir1 == right1) {
if (ship1.getX() + ship1.getWidth() >= win_size - speed) {
dir1 = stop1;
} else {
ship1.setLocation(ship1.getX() + speed, ship1.getY());
}
}
if (dir2 == left2) {
if (ship2.getX() <= speed) {
dir2 = stop2;
} else {
ship2.setLocation(ship2.getX() - speed, ship2.getY());
}
} else if (dir2 == right2) {
if (ship2.getX() + ship2.getWidth() >= win_size - speed) {
dir2 = stop2;
} else {
ship2.setLocation(ship2.getX() + speed, ship2.getY());
}
}
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == timer) {
System.out.println("Timer Working!");
move();
}
}
}
但是当你释放一个键时玩家不会停止移动......
嗯,是的,确实如此,但原始代码似乎也没有该功能
因此,在PlayervsPlayer 构造函数中,我们将现有的绑定替换为类似...
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "player1-left-pressed");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "player1-right-pressed");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "player2-left-pressed");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_E, 0, false), "player2-right-pressed");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "player1-left-released");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "player1-right-released");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "player2-left-released");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_E, 0, true), "player2-right-released");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "space");
// I'd prefer this to be a self containted unit of work, but for demonstration purposes
actionMap.put("player1-left-pressed", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Player 1 - left");
dir1 = left1;
}
});
actionMap.put("player1-right-pressed", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Player 1 - right");
dir1 = right1;
}
});
actionMap.put("player2-left-pressed", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Player 2 - left");
dir2 = left2;
}
});
actionMap.put("player2-right-pressed", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Player 2 - right");
dir2 = right2;
}
});
actionMap.put("player1-left-released", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Player 1 - left");
dir1 = stop1;
}
});
actionMap.put("player1-right-released", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Player 1 - stop");
dir1 = stop1;
}
});
actionMap.put("player2-left-released", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Player 2 - stop");
dir2 = stop2;
}
});
actionMap.put("player2-right-released", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Player 2 - stop");
dir2 = stop2;
}
});
actionMap.put("space", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Pew pew pew");
}
});
我还添加了 Space 因为我忘记放在前面的例子中
我很想看看我的代码的键绑定有多长
我最喜欢的主题 - 减少和重用。正如我在上面的 cmets 中所说,我希望有一个可以改变某种状态的“移动动作”类,这里我使用了 Set,但你可以传递一个 PlayervsPlayer 的实例和让MoveAction 在其上调用一个方法,告诉类发生了什么操作,我更喜欢这种方法,因为它可以解耦代码(使其更可重用)
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.Set;
import java.util.TreeSet;
// I'd prefer to extend from JPanel, as it provides bases for
// self contained responsibility, but apparently, that's too "advanced"
public class PlayervsPlayer implements ActionListener {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
frame.setLayout(null);
new PlayervsPlayer(frame, 400).pvpmain();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
private JFrame window;
private Timer timer;
private int win_size;
private int ship_clearence = 45;
private int speed = 3;
private final int stop1 = 0;
private final int stop2 = 1;
private final int left1 = 2;
private final int left2 = 3;
private final int right1 = 4;
private final int right2 = 5;
// private int dir1 = left1;
// private int dir2 = right2;
// Sprite background;
JLabel ship1 = new JLabel("Spaceship.png");
JLabel ship2 = new JLabel("Spaceship2.png");
private Set<Integer> playerKeys;
public PlayervsPlayer(JFrame w, int win_s) {
window = w;
// background = backdrop;
win_size = win_s;
playerKeys = new TreeSet<>();
playerKeys = new TreeSet<>();
playerKeys.add(left1);
playerKeys.add(right2);
JComponent contenPane = (JComponent) w.getContentPane();
InputMap inputMap = contenPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = contenPane.getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "player1-left");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "player1-right");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0), "player2-left");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_E, 0), "player2-right");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "player1-left-pressed");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "player1-right-pressed");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "player2-left-pressed");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_E, 0, false), "player2-right-pressed");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "player1-left-released");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "player1-right-released");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "player2-left-released");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_E, 0, true), "player2-right-released");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "space");
// I'd prefer this to be a self containted unit of work, but for demonstration purposes
actionMap.put("player1-left-pressed", new MoveAction(playerKeys, left1, false));
actionMap.put("player1-right-pressed", new MoveAction(playerKeys, right1, false));
actionMap.put("player2-left-pressed", new MoveAction(playerKeys, left2, false));
actionMap.put("player2-right-pressed", new MoveAction(playerKeys, right2, false));
actionMap.put("player1-left-released", new MoveAction(playerKeys, left1, true));
actionMap.put("player1-right-released", new MoveAction(playerKeys, right1, true));
actionMap.put("player2-left-released", new MoveAction(playerKeys, left2, true));
actionMap.put("player2-right-released", new MoveAction(playerKeys, right2, true));
actionMap.put("space", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Pew pew pew");
}
});
}
public void pvpmain() {
System.out.println("Player vs Player working!");
ship1.setSize(125, 125);
ship1.setLocation((int) ((win_size / 2) - (ship1.getWidth() / 2)), win_size - ship1.getHeight() - ship_clearence);
ship2.setSize(125, 125);
ship2.setLocation((int) ((win_size / 2) - (ship1.getWidth() / 2)), 0);
window.add(ship1, 0);
window.add(ship2, 0);
window.repaint();
timer = new Timer(10, this);
timer.start();
}
public void move() {
if (playerKeys.contains(left1)) {
if (ship1.getX() <= speed) {
playerKeys.clear();
} else {
ship1.setLocation(ship1.getX() - speed, ship1.getY());
}
} else if (playerKeys.contains(right1)) {
if (ship1.getX() + ship1.getWidth() >= win_size - speed) {
playerKeys.clear();
} else {
ship1.setLocation(ship1.getX() + speed, ship1.getY());
}
}
if (playerKeys.contains(left2)) {
if (ship2.getX() <= speed) {
playerKeys.clear();
} else {
ship2.setLocation(ship2.getX() - speed, ship2.getY());
}
} else if (playerKeys.contains(right2)) {
if (ship2.getX() + ship2.getWidth() >= win_size - speed) {
playerKeys.clear();
} else {
ship2.setLocation(ship2.getX() + speed, ship2.getY());
}
}
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == timer) {
move();
}
}
public class MoveAction extends AbstractAction {
private Set<Integer> keys;
private Integer action;
private boolean released;
public MoveAction(Set<Integer> keys, Integer action, boolean released) {
this.keys = keys;
this.action = action;
this.released = released;
}
@Override
public void actionPerformed(ActionEvent e) {
if (released) {
keys.remove(action);
} else {
keys.add(action);
}
}
}
}