J'utilise KeyListener
s dans mon code (jeu ou autre) pour permettre à mes objets à l'écran de réagir à la saisie par la clé d'utilisateur. Voici mon code:
public class MyGame extends JFrame {
static int up = KeyEvent.VK_UP;
static int right = KeyEvent.VK_RIGHT;
static int down = KeyEvent.VK_DOWN;
static int left = KeyEvent.VK_LEFT;
static int fire = KeyEvent.VK_Q;
public MyGame() {
// Do all the layout management and what not...
JLabel obj1 = new JLabel();
JLabel obj2 = new JLabel();
obj1.addKeyListener(new MyKeyListener());
obj2.addKeyListener(new MyKeyListener());
add(obj1);
add(obj2);
// Do other GUI things...
}
static void move(int direction, Object source) {
// do something
}
static void fire(Object source) {
// do something
}
static void rebindKey(int newKey, String oldKey) {
// Depends on your GUI implementation.
// Detecting the new key by a KeyListener is the way to go this time.
if (oldKey.equals("up"))
up = newKey;
if (oldKey.equals("down"))
down = newKey;
// ...
}
public static void main(String[] args) {
new MyGame();
}
private static class MyKeyListener extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
Object source = e.getSource();
int action = e.getExtendedKeyCode();
/* Will not work if you want to allow rebinding keys since case variables must be constants.
switch (action) {
case up:
move(1, source);
case right:
move(2, source);
case down:
move(3, source);
case left:
move(4, source);
case fire:
fire(source);
...
}
*/
if (action == up)
move(1, source);
else if (action == right)
move(2, source);
else if (action == down)
move(3, source);
else if (action == left)
move(4, source);
else if (action == fire)
fire(source);
}
}
}
J'ai des problèmes de réactivité:
Pourquoi cela se produit-il et comment résoudre ce problème?
_ {Cette réponse explique et montre comment utiliser des liaisons de touches au lieu d'écouteurs de touches à des fins pédagogiques. Ce n'est pas le cas
Il est
Réponse; Lisez le didacticiel Swing sur les raccourcis clavier .
Je ne veux pas lire les manuels, dites-moi pourquoi je voudrais utiliser des raccourcis clavier au lieu du beau code que j'ai déjà!
Eh bien, le tutoriel Swing explique que
OK, vous m'avez convaincu d'essayer. Comment ça marche?
Le tutorial contient une bonne section à ce sujet. Les raccourcis clavier impliquent 2 objets InputMap
et ActionMap
. InputMap
mappe une entrée utilisateur sur un nom d'action, ActionMap
mappe un nom d'action sur Action
. Lorsque l'utilisateur appuie sur une touche, la mappe d'entrée est recherchée pour la clé et trouve un nom d'action, puis la mappe d'action est recherchée pour le nom de l'action et exécute l'action.
Ça a l'air lourd. Pourquoi ne pas lier directement l'entrée de l'utilisateur à l'action et supprimer le nom de l'action? Ensuite, vous n’avez besoin que d’une seule carte et non de deux.
Bonne question! Vous verrez que c'est l'une des choses qui rendent les liaisons de clé plus faciles à gérer (désactiver, relier, etc.).
Je veux que vous me donniez un code de travail complet.
Non (le tutoriel Swing a des exemples de travail ).
Tu crains!Je te deteste!
Voici comment créer une liaison de clé unique:
myComponent.getInputMap().put("userInput", "myAction");
myComponent.getActionMap().put("myAction", action);
Notez qu'il y a 3 InputMap
s réagissant à différents états de focus:
myComponent.getInputMap(JComponent.WHEN_FOCUSED);
myComponent.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
myComponent.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
WHEN_FOCUSED
, qui est également celui utilisé lorsqu'aucun argument n'est fourni, est utilisé lorsque le composant a le focus. Ceci est similaire au cas d'écoute de clé.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
est utilisé lorsqu'un composant ciblé est à l'intérieur d'un composant qui est enregistré pour recevoir l'action. Si vous avez plusieurs membres d'équipage à l'intérieur d'un vaisseau spatial et que vous voulez que le vaisseau spatial continue à recevoir des informations pendant que l'un des membres de l'équipage a le focus, utilisez ceci.WHEN_IN_FOCUSED_WINDOW
est utilisé lorsqu'un composant enregistré pour recevoir l'action se trouve à l'intérieur d'un composant ciblé. Si vous avez plusieurs réservoirs dans une fenêtre ciblée et que vous souhaitez que tous reçoivent une entrée en même temps, utilisez ceci.Le code présenté dans la question ressemblera à quelque chose comme ceci en supposant que les deux objets doivent être contrôlés en même temps:
public class MyGame extends JFrame {
private static final int IFW = JComponent.WHEN_IN_FOCUSED_WINDOW;
private static final String MOVE_UP = "move up";
private static final String MOVE_DOWN = "move down";
private static final String FIRE = "move fire";
static JLabel obj1 = new JLabel();
static JLabel obj2 = new JLabel();
public MyGame() {
// Do all the layout management and what not...
obj1.getInputMap(IFW).put(KeyStroke.getKeyStroke("UP"), MOVE_UP);
obj1.getInputMap(IFW).put(KeyStroke.getKeyStroke("DOWN"), MOVE_DOWN);
// ...
obj1.getInputMap(IFW).put(KeyStroke.getKeyStroke("control CONTROL"), FIRE);
obj2.getInputMap(IFW).put(KeyStroke.getKeyStroke("W"), MOVE_UP);
obj2.getInputMap(IFW).put(KeyStroke.getKeyStroke("S"), MOVE_DOWN);
// ...
obj2.getInputMap(IFW).put(KeyStroke.getKeyStroke("T"), FIRE);
obj1.getActionMap().put(MOVE_UP, new MoveAction(1, 1));
obj1.getActionMap().put(MOVE_DOWN, new MoveAction(2, 1));
// ...
obj1.getActionMap().put(FIRE, new FireAction(1));
obj2.getActionMap().put(MOVE_UP, new MoveAction(1, 2));
obj2.getActionMap().put(MOVE_DOWN, new MoveAction(2, 2));
// ...
obj2.getActionMap().put(FIRE, new FireAction(2));
// In practice you would probably create your own objects instead of the JLabels.
// Then you can create a convenience method obj.inputMapPut(String ks, String a)
// equivalent to obj.getInputMap(IFW).put(KeyStroke.getKeyStroke(ks), a);
// and something similar for the action map.
add(obj1);
add(obj2);
// Do other GUI things...
}
static void rebindKey(KeyEvent ke, String oldKey) {
// Depends on your GUI implementation.
// Detecting the new key by a KeyListener is the way to go this time.
obj1.getInputMap(IFW).remove(KeyStroke.getKeyStroke(oldKey));
// Removing can also be done by assigning the action name "none".
obj1.getInputMap(IFW).put(KeyStroke.getKeyStrokeForEvent(ke),
obj1.getInputMap(IFW).get(KeyStroke.getKeyStroke(oldKey)));
// You can drop the remove action if you want a secondary key for the action.
}
public static void main(String[] args) {
new MyGame();
}
private class MoveAction extends AbstractAction {
int direction;
int player;
MoveAction(int direction, int player) {
this.direction = direction;
this.player = player;
}
@Override
public void actionPerformed(ActionEvent e) {
// Same as the move method in the question code.
// Player can be detected by e.getSource() instead and call its own move method.
}
}
private class FireAction extends AbstractAction {
int player;
FireAction(int player) {
this.player = player;
}
@Override
public void actionPerformed(ActionEvent e) {
// Same as the fire method in the question code.
// Player can be detected by e.getSource() instead, and call its own fire method.
// If so then remove the constructor.
}
}
}
Vous pouvez constater que la séparation de la mappe d'entrée de la mappe d'action permet un code réutilisable et un meilleur contrôle des liaisons. En outre, vous pouvez également contrôler une action directement si vous avez besoin de cette fonctionnalité. Par exemple:
FireAction p1Fire = new FireAction(1);
p1Fire.setEnabled(false); // Disable the action (for both players in this case).
Voir le didacticiel Action pour plus d'informations.
Je vois que vous avez utilisé 1 action, déplacer, pour 4 touches (directions) et 1 action, feu, pour 1 touche. Pourquoi ne pas donner à chaque touche sa propre action, ou donner à toutes les touches la même action et déterminer ce qu'il faut faire dans l'action (comme dans le cas d'un mouvement)?
Bon point. Techniquement, vous pouvez faire les deux, mais vous devez penser à ce qui a du sens et à ce qui permet une gestion facile et un code réutilisable. Ici, j'ai supposé que le déplacement était similaire dans toutes les directions et que le tir était différent, j'ai donc choisi cette approche.
Je vois beaucoup de
KeyStroke
s utilisés, quels sont-ils? Sont-ils comme unKeyEvent
?
Oui, ils ont une fonction similaire, mais sont plus appropriés pour une utilisation ici. Voir leur API pour plus d’informations et pour savoir comment les créer.
_ {Questions? Améliorations? Suggestions? Laisser un commentaire . Vous avez une meilleure réponse? Postez-le.} _
Note: ceci est pas une réponse, juste un commentaire avec trop de code :-)
Obtenir keyStrokes via getKeyStroke (String) est la méthode correcte - mais nécessite une lecture attentive du document api:
modifiers := shift | control | ctrl | meta | alt | altGraph
typedID := typed <typedKey>
typedKey := string of length 1 giving Unicode character.
pressedReleasedID := (pressed | released) key
key := KeyEvent key code name, i.e. the name following "VK_".
Il est préférable que la dernière ligne soit nom exact , c’est le cas: pour la touche bas le nom exact du code est VK_DOWN
, le paramètre doit donc être "DOWN" (pas "Down" ou toute autre variante de haut/minuscules)
Pas tout à fait intuitif (lire: j'ai dû creuser un peu moi-même), c'est obtenir un KeyStroke à une touche de modificateur. Même avec une orthographe correcte, les éléments suivants ne fonctionneront pas:
KeyStroke control = getKeyStroke("CONTROL");
Plus profondément dans la file d'attente d'événements awt, un événement keyEvent pour une seule touche de modificateur est créé avec lui-même en tant que modificateur. Pour vous lier à la touche de contrôle, vous avez besoin du trait:
KeyStroke control = getKeyStroke("ctrl CONTROL");
Voici un moyen facile qui ne vous obligerait pas à lire des centaines de lignes de code, mais apprenez un tour de quelques lignes.
déclarer un nouveau JLabel et l'ajouter à votre JFrame (je ne l'ai pas testé dans d'autres composants)
private static JLabel listener= new JLabel();
L'accent doit rester sur ce point pour que les clés fonctionnent.
En constructeur:
add(listener);
Utilisez cette méthode:
ANCIENNE METHODE:
private void setKeyBinding(String keyString, AbstractAction action) {
listener.getInputMap().put(KeyStroke.getKeyStroke(keyString), keyString);
listener.getActionMap().put(keyString, action);
}
KeyString doit être écrit correctement. Ce n'est pas typé et vous devez consulter la liste officielle list pour savoir quel est le keyString (ce n'est pas un terme officiel) pour chaque bouton.
NOUVELLE METHODE
private void setKeyBinding(int keyCode, AbstractAction action) {
int modifier = 0;
switch (keyCode) {
case KeyEvent.VK_CONTROL:
modifier = InputEvent.CTRL_DOWN_MASK;
break;
case KeyEvent.VK_SHIFT:
modifier = InputEvent.SHIFT_DOWN_MASK;
break;
case KeyEvent.VK_ALT:
modifier = InputEvent.ALT_DOWN_MASK;
break;
}
listener.getInputMap().put(KeyStroke.getKeyStroke(keyCode, modifier), keyCode);
listener.getActionMap().put(keyCode, action);
}
Dans cette nouvelle méthode, vous pouvez simplement le définir en utilisant KeyEvent.VK_WHATEVER
EXEMPLE D'APPEL:
setKeyBinding(KeyEvent.VK_CONTROL, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("ctrl pressed");
}
});
Envoyez une classe anonyme (ou utilisez une sous-classe) de AbstractAction. Remplacez sa public void actionPerformed(ActionEvent e)
et faites-la faire ce que vous voulez que la clé fasse.
PROBLEME:
Je ne pouvais pas le faire fonctionner pour VK_ALT_GRAPH.
case KeyEvent.VK_ALT_GRAPH:
modifier = InputEvent.ALT_GRAPH_DOWN_MASK;
break;
ne le fait pas fonctionner pour moi pour une raison quelconque.