KeyListener/KeyBinding不一致触发 [英] KeyListener/KeyBinding does not trigger consistently

查看:90
本文介绍了KeyListener/KeyBinding不一致触发的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试制作一个简单的游戏,我需要飞船在其中心旋转(使用A和D键),并相对于其面对的方向(W和S键)移动.

I am trying to make a simple game, and i need my spaceship to rotate on its centre (using A and D keys) , and move relative to the direction it is facing (W and S Keys).

我大致了解了运动和旋转的数学原理,但是关键听众却遇到了问题.

I got the math for the movement and rotation mostly figured out, but im having an issue with the key listeners.

当我编译时,按键监听器可以在前几次按键操作中正常工作.但是,如果我按住A键(在被按下的同时向左旋转),然后切换到D键,然后再次切换到A键,它将停止工作.按键的任何其他组合也会导致这种情况.

When i compile, the key listener works fine for th e first couple of key presses. But if i hold down A (rotate left while being pressed down) and then switch to D and then switch to A again, it stops working. Any other combination of key presses also results in this.

基本上,如果我按太多键,最终它将停止工作.如果我走慢,按几次我想工作的键后它将重新开始工作.但是,如果我立即按一堆键(依次按A,D,W),则它完全停止工作,并且所有键均无响应.

Basically, if i press the keys too much eventually it will stop working. If i go slow, it will start to work again after i press the key i want to work a few times. BUT if i press a bunch of keys at once (hit A, then D, then W in quick succession), then it completely stops working, and all keys become unresponsive.

我认为这不是处理速度问题,因为程序不会崩溃,也不会产生任何内存错误.

This is not a processing speed issue i think, as the program does not crash, or produce any memory errors.

我已经阅读了类似的问题,发现人们建议不要使用KeyListeners,而应使用KeyBindings,但是我尝试了两者,并得到了完全相同的问题.

I have read up on similar issues, and saw that people suggested to not use KeyListeners, and use KeyBindings instead, but i tried both and got the exact same issues.

这是我的程序的更新代码(试图制作MCVE): 我在中间注释掉的部分是用于绑定的代码.

Here is the UPDATED code for my program (tried to make an MCVE): The portion i commented out in the middle was the code for the keybinding.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.math.*;
import java.net.URL;

public class Game extends JPanel implements ActionListener{

private Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
JFrame frame = new JFrame();
private Ship ship = new Ship();
private Timer gameTimer, turnRight, turnLeft, moveForward, moveBackward;
private static final int IFW = JComponent.WHEN_IN_FOCUSED_WINDOW;

public Game(){
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setTitle("Study Buddy Menu");
    frame.setSize(800,700);
    frame.setLocation(dim.width/2 - 400, dim.height/2 - 350);

    turnLeft = new Timer(20, new ActionListener(){
        public void actionPerformed(ActionEvent e) {
            ship.setAngle(ship.getAngle() + ship.getTurnSpeed()); 
        }
    });
    turnRight = new Timer(20, new ActionListener(){
        public void actionPerformed(ActionEvent e) {
            ship.setAngle(ship.getAngle() - ship.getTurnSpeed()); 
        }
    });
    moveForward = new Timer(20, new ActionListener(){
        public void actionPerformed(ActionEvent e) {
            ship.setX(ship.getX() +  (float)Math.cos(Math.toRadians(ship.getAngle())));
            ship.setY(ship.getY() -  (float)Math.sin(Math.toRadians(ship.getAngle())));
            System.out.println("UP");
        }
    });
    moveBackward = new Timer(20, new ActionListener(){
        public void actionPerformed(ActionEvent e) {
            ship.setX(ship.getX() -  (float)Math.cos(Math.toRadians(ship.getAngle())));
            ship.setY(ship.getY() +  (float)Math.sin(Math.toRadians(ship.getAngle())));
            System.out.println("DOWN");
        }
    });
    this.setFocusable(true);
    this.requestFocus();
    /*getInputMap(IFW).put(KeyStroke.getKeyStroke('d'), "right");
    getActionMap().put("right", new MoveAction("d"));
    getInputMap(IFW).put(KeyStroke.getKeyStroke('a'), "left");
    getActionMap().put("left", new MoveAction("a"));
    getInputMap(IFW).put(KeyStroke.getKeyStroke('w'), "up");
    getActionMap().put("up", new MoveAction("w"));
    getInputMap(IFW).put(KeyStroke.getKeyStroke('s'), "down");
    getActionMap().put("down", new MoveAction("s"));*/

    this.addKeyListener(new KeyAdapter(){
        public void keyPressed(KeyEvent e){
            //TURN
            if(e.getKeyCode() == KeyEvent.VK_D){
                turnLeft.start();   
                turnRight.stop();
            }
            if(e.getKeyCode() == KeyEvent.VK_A){ 
                turnRight.start();
                turnLeft.stop();
                }
            //MOVE
            if(e.getKeyCode() == KeyEvent.VK_W){
                moveForward.start();
                moveBackward.stop();
            }
            if(e.getKeyCode() == KeyEvent.VK_S){
                moveBackward.start();
                moveForward.stop();
            }

        }
        public void keyReleased(KeyEvent e){
            turnRight.stop();
            turnLeft.stop();
            moveForward.stop();
            moveBackward.stop();
        }
    });
    frame.add(this);
    repaint();
    gameTimer = new Timer(20, this);
    gameTimer.start();
    frame.setVisible(true);
}

public void paintComponent(Graphics g){
    super.paintComponent(g);
    ship.draw(g);
}
public void actionPerformed(ActionEvent e) {
    repaint();

}
private class MoveAction extends AbstractAction {
    String d = null;
    MoveAction(String direction) {
        d = direction;
    } 
    public void actionPerformed(ActionEvent e) {
        switch (d){
            case "d": turnLeft.start(); turnRight.stop();break;
            case "a": turnRight.start(); turnLeft.stop();break;
            case "w": moveForward.start(); moveBackward.stop();break;
            case "s": moveBackward.start(); moveForward.stop();break;
        }

    }

}
class main {

public void main(String[] args) {
    Game game = new Game();
}

}

class Ship {
private float x, y, radius, speed, angle, turnSpeed;
private Image icon;
public Ship(){
    x = 400;
    y = 350;
    speed = 1;
    radius = 20;
    angle = 90;
    turnSpeed = 5;
    try {
        icon = ImageIO.read(new URL("https://i.stack.imgur.com/L5DGx.png"));
    } catch (IOException e) {
        e.printStackTrace();
    }
}


//GETTERS
public float getX(){
    return x;
}
public float getY(){
    return y;
}
public float getTurnSpeed(){
    return turnSpeed;
}
public float getAngle(){
    return angle;
}
public float getRadius(){
    return radius;
}
public float getSpeed(){
    return speed;
}
public Image getIcon(){
    return icon;
}
//SETTERS
public void setX(float X){
    x = X;
}
public void setTurnSpeed(float S){
    turnSpeed = S;
}
public void setY(float Y){
    y = Y;
}
public void setAngle(float A){
    angle = A;
}
public void setSpeed(float S){
    speed = S;
}
public void setRadius(float R){
    radius = R;
}
public void setIcon(Image image){
    icon = image;
}
//DRAW
public void draw(Graphics g){
    Graphics2D g2d = (Graphics2D) g;
    AffineTransform at = new AffineTransform();
    at.setToRotation(Math.toRadians(angle-90), x, y);
    //at.translate(x, y);
    g2d.setTransform(at);
    g2d.drawImage(icon,(int)(x - radius), (int)(y - radius),(int)(radius * 2),(int)(radius * 2), null);
    g2d.dispose();
}
}
}

推荐答案

我正要上床睡觉,所以我不会对您的代码有太多了解,但是行为良好的一种方法是像这样:

I'm just about to go to bed so I can't look too deeply in to your code, but a well-behaved method for doing movement is something like this:

  • 您的KeyListener/键绑定/无论设置什么变量,例如isTurningLeftisTurningRightisMovingForwardsisMovingBackwards.
  • 然后,您只有一个计时器,它可以在每次迭代时检查控制状态,并同时基于所有计时器来计算运动.
  • Your KeyListener/key binding/whatever sets some variables like isTurningLeft, isTurningRight, isMovingForwards, isMovingBackwards.
  • Then you have just one timer that checks the control state on every iteration and computes the movement based on all of it at the same time.

同时跟踪每个按钮很重要,这样您才能获得所有信息.因此,例如,您可以执行以下操作:

Keeping track of every button simultaneously is important so that you have all the information. So for example, you could do something like this:

double turnRate = 0;
if (isTurningLeft) {
    turnRate -= maxTurnRate;
}
if (isTurningRight) {
    turnRate += maxTurnRate;
}
rotationAngle += timeElapsed * turnRate;

这样,如果两个按钮同时按住,则转弯速率仅为0.这将避免许多奇怪的行为,例如粘滞和结结.但是,要做到这一点,您需要将两个按钮的状态都放在一个位置.

This way if both buttons are held at the same time, the turn rate is just 0. That will avoid a lot of weird sorts of behaviors, like sticking and stuttering. However, to do that you need to know the state of both buttons in one place.

编辑

我修改了您的程序作为示例:

I modified your program to serve as an example:

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.net.URL;

public class KeyListenerGame extends JPanel implements ActionListener{
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new KeyListenerGame();
            }
        });
    }

//    private static final int IFW = JComponent.WHEN_IN_FOCUSED_WINDOW;
//    private Dimension   dim     = Toolkit.getDefaultToolkit().getScreenSize();
    private JFrame frame;
    private Ship   ship;
    private Timer  gameTimer;//, turnRight, turnLeft, moveForward, moveBackward;
    private KeyBindingController controller;

    // This is just an example. I recommend using
    // the key bindings instead, even though they
    // are a little more complicated to set up.
    class KeyListenerController extends KeyAdapter {
        boolean isTurningLeft;
        boolean isTurningRight;
        boolean isMovingForward;
        boolean isMovingBackward;

        @Override
        public void keyPressed(KeyEvent e) {
            switch (e.getKeyCode()) {
                case KeyEvent.VK_A:
                    isTurningLeft = true;
                    break;
                case KeyEvent.VK_D:
                    isTurningRight = true;
                    break;
                case KeyEvent.VK_W:
                    isMovingForward = true;
                    break;
                case KeyEvent.VK_S:
                    isMovingBackward = true;
                    break;
            }
        }
        @Override
        public void keyReleased(KeyEvent e) {
            switch (e.getKeyCode()) {
                case KeyEvent.VK_A:
                    isTurningLeft = false;
                    break;
                case KeyEvent.VK_D:
                    isTurningRight = false;
                    break;
                case KeyEvent.VK_W:
                    isMovingForward = false;
                    break;
                case KeyEvent.VK_S:
                    isMovingBackward = false;
                    break;
            }
        }
    }

    // This could be simplified a lot with Java 8 features.
    class KeyBindingController {
        boolean isTurningLeft;
        boolean isTurningRight;
        boolean isMovingForward;
        boolean isMovingBackward;

        JComponent component;

        KeyBindingController(JComponent component) {
            this.component = component;
            // Bind key pressed Actions.
            bind(KeyEvent.VK_A, true, new AbstractAction("turnLeft.pressed") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    isTurningLeft = true;
                }
            });
            bind(KeyEvent.VK_D, true, new AbstractAction("turnRight.pressed") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    isTurningRight = true;
                }
            });
            bind(KeyEvent.VK_W, true, new AbstractAction("moveForward.pressed") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    isMovingForward = true;
                }
            });
            bind(KeyEvent.VK_S, true, new AbstractAction("moveBackward.pressed") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    isMovingBackward = true;
                }
            });
            // Bind key released Actions.
            bind(KeyEvent.VK_A, false, new AbstractAction("turnLeft.released") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    isTurningLeft = false;
                }
            });
            bind(KeyEvent.VK_D, false, new AbstractAction("turnRight.released") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    isTurningRight = false;
                }
            });
            bind(KeyEvent.VK_W, false, new AbstractAction("moveForward.released") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    isMovingForward = false;
                }
            });
            bind(KeyEvent.VK_S, false, new AbstractAction("moveBackward.released") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    isMovingBackward = false;
                }
            });
        }

        void bind(int keyCode, boolean onKeyPress, Action action) {
            KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, 0, !onKeyPress);
            String   actionName = (String) action.getValue(Action.NAME);
            component.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
                .put(keyStroke, actionName);
            component.getActionMap()
                .put(actionName, action);
        }
    }

    public KeyListenerGame(){
        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setTitle("Study Buddy Menu");

        frame.add(this);
        frame.pack();
        frame.setLocationRelativeTo(null);

        gameTimer  = new Timer(20, this);
        controller = new KeyBindingController(this);
        ship       = new Ship(getSize());

        gameTimer.start();
        frame.setVisible(true);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        double secsElapsed = gameTimer.getDelay() / 1000.0;

        double maxSpeed     = ship.getSpeed();
        double maxTurnSpeed = ship.getTurnSpeed();
        double theta = Math.toRadians( ship.getAngle() );
        double x     = ship.getX();
        double y     = ship.getY();

        double turnSpeed = 0;
        if (controller.isTurningLeft) {
            turnSpeed -= maxTurnSpeed;
        }
        if (controller.isTurningRight) {
            turnSpeed += maxTurnSpeed;
        }

        theta += secsElapsed * Math.toRadians(turnSpeed);

        double speed = 0;
        if (controller.isMovingForward) {
            speed += maxSpeed;
        }
        if (controller.isMovingBackward) {
            speed -= maxSpeed;
        }

        double velX = speed * Math.cos(theta);
        double velY = speed * Math.sin(theta);

        x += secsElapsed * velX;
        y += secsElapsed * velY;

        ship.setX( (float) x );
        ship.setY( (float) y);
        ship.setAngle( (float) Math.toDegrees(theta) );

        repaint();
    }

    @Override
    public Dimension getPreferredSize() {
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        // Computes a preferred size based on the
        // dimensions of the screen size.
        int prefWidth  = screenSize.width / 2;
        // Compute 4:3 aspect ratio.
        int prefHeight = prefWidth * 3 / 4;
        return new Dimension(prefWidth, prefHeight);
    }

    @Override
    protected void paintComponent(Graphics g){
        super.paintComponent(g);
        ship.draw(g);
    }

    class Ship {
        private float x, y, radius, speed, angle, turnSpeed;
        private Image icon;
        public Ship(Dimension gameSize) {
//            x = 400;
//            y = 350;
//            speed = 1;
//            radius = 20;
//            angle = 90;
//            turnSpeed = 5;

            x = gameSize.width  / 2;
            y = gameSize.height / 2;
            radius = 20;
            angle  = -90;
            // 1/4 of the game height per second
            speed  = gameSize.height / 4;
            // 180 degrees per second
            turnSpeed = 180;

            try {
                icon = ImageIO.read(new URL("http://i.stack.imgur.com/L5DGx.png"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        //GETTERS
        public float getX(){
            return x;
        }
        public float getY(){
            return y;
        }
        public float getTurnSpeed(){
            return turnSpeed;
        }
        public float getAngle(){
            return angle;
        }
        public float getRadius(){
            return radius;
        }
        public float getSpeed(){
            return speed;
        }
        public Image getIcon(){
            return icon;
        }
        //SETTERS
        public void setX(float X){
            x = X;
        }
        public void setTurnSpeed(float S){
            turnSpeed = S;
        }
        public void setY(float Y){
            y = Y;
        }
        public void setAngle(float A){
            angle = A;
        }
        public void setSpeed(float S){
            speed = S;
        }
        public void setRadius(float R){
            radius = R;
        }
        public void setIcon(Image image){
            icon = image;
        }
        //DRAW
        public void draw(Graphics g){
            Graphics2D g2d = (Graphics2D) g.create();
            //
            // Draw the ship's movement vector.
            double theta = Math.toRadians(angle);
            double velX  = speed * Math.cos(theta);
            double velY  = speed * Math.sin(theta);
            g2d.setColor(java.awt.Color.blue);
            g2d.draw(new java.awt.geom.Line2D.Double(x, y, x + velX, y + velY));
            //
            g2d.rotate(theta, x, y);
            int imgX = (int) ( x - radius );
            int imgY = (int) ( y - radius );
            int imgW = (int) ( 2 * radius );
            int imgH = (int) ( 2 * radius );
            g2d.drawImage(icon, imgX, imgY, imgW, imgH, null);
            //
            g2d.dispose();
        }
    }
}

除了控件之外,我还更改了其他一些内容:

In addition to the controls, I changed some other things:

  • 始终通过调用SwingUtilities.invokeLater(...)来启动Swing程序.这是因为Swing在与运行main的线程不同的线程上运行. https://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
  • 我没有直接设置JFrame的大小,而是改写了getPreferredSize并根据屏幕尺寸计算了初始大小,然后在JFrame上称为pack().这比尝试静态设置像素大小要好得多.另外,JFrame的大小包括其插图(例如标题栏),因此如果您调用例如jFrame.setSize(800,600)显示游戏的内容窗格实际上实际上比800x600小.
  • 我添加了一行以显示船的运动矢量,以直观显示发生的情况.这种事情很适合调试.
  • ship.draw中,我用g.create()创建了g的副本,因为g是Swing亲自使用的图形对象.在传入的Graphics上进行诸如处置或设置转换之类的操作不是一个好主意.
  • 我固定了一点轮换代码.由于AWT坐标中的y轴是上下颠倒的,因此,正角实际上是顺时针旋转,而负角实际上是逆时针旋转.
  • Always begin a Swing program with a call to SwingUtilities.invokeLater(...). This is because Swing runs on a different thread than the one which main gets run on. https://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
  • Instead of setting the size of a JFrame directly, I overrode getPreferredSize and computed an initial size based on the screen dimensions, then called pack() on the JFrame. This is much more well-behaved than trying to set pixel sizes statically. Also, the size of a JFrame includes its insets (for example the title bar), so if you call e.g. jFrame.setSize(800,600) the content pane which displays the game actually ends up being a little smaller than 800x600.
  • I added a line which displays the ship's movement vector, just to visualize what's going on. This sort of thing is nice for debugging.
  • In ship.draw I create a copy of g with g.create(), because g is the graphics object which Swing personally uses. Doing things like disposing or setting a transform on the Graphics passed in isn't a good idea.
  • I fixed the rotation code a little. Because the y-axis in AWT coordinates is upside-down, positive angles actually rotate clockwise and negative angles rotate counter-clockwise.

有很多方法可以使键绑定代码更好,但是我只是最明显的方法,因此您可以清楚地看到我的实际操作.您必须为按下和释放的事件设置绑定.不过,您会注意到它有一个模式,因此您可以编写一个小类来执行通用布尔逻辑,而不是像我一样复制粘贴.

There are ways to make the key binding code nicer, but I just did it the most obvious way so you can clearly see what I was actually doing. You have to set up bindings both for pressed and released events. You'll notice, though, that there is a pattern to it, so you could program a small class to perform the generic boolean logic instead of copy-and-pasting like I did.

Java 8 lambda也很不错,但是我的Java 8计算机坏了.使用Java 8,您最终可能会遇到类似这样的事情:

Java 8 lambdas would also be very nice, but my computer with Java 8 is broken. With Java 8 you could end up with something like this, though:

bind(KeyEvent.VK_A, "moveLeft", b -> isMovingLeft = b);

并且:

void bind(int keyCode, String name, Consumer<Boolean> c) {
    Action pressAction = new AbstractAction(name + ".pressed") {
        @Override
        public void actionPerformed(ActionEvent e) {
            c.accept(true);
        }
    };
    Action releaseAction = new AbstractAction(name + ".released") {
        @Override
        public void actionPerformed(ActionEvent e) {
            c.accept(false);
        }
    };
    // Then bind it to the InputMap/ActionMap like I did
    // in the MCVE.
}

这篇关于KeyListener/KeyBinding不一致触发的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆