Continuous movement with a single key press?

2020-03-31 05:04发布

I'm trying to make a program in java that involves making an object move constantly from a single key press. Think Pacman, where you press up once and Pacman continues to go up until you press another key. I want to keep the code simple if possible. My original movement (one keypress = one movement) is like this:

public class AL extends KeyAdapter {
    public void keyPressed(KeyEvent e){
        int keyCode = e.getKeyCode();
        if(keyCode == e.VK_A){
            x -= 5;
        }
        if(keyCode == e.VK_D){
            x += 5;
        }
        if(keyCode == e.VK_W){
            y -= 5;
        }
        if(keyCode == e.VK_S){
            y += 5;
        }

    }

The x and y in values are the position of an oval. This works perfectly, but I want it to keep moving after I press the key only once, instead of having to hold it to keep the movement going. I tried a while loop with a boolean parameter that moves while true and doesn't while false, but as soon as I activate the loop, it freezes the program. Here's an example of that bit of code:

public void keyPressed(KeyEvent e){
        int keyCode = e.getKeyCode();
        if(keyCode == e.VK_LEFT && moveL==false){
           moveL=true;
           moveR=false;
           moveU=false;
           moveD=false;
           while(moveL){
            x--;
            }
        }

Please help me figure this out, I've been trying and looking around for days now. I appreciate any help you guys can give. Thanks.

2条回答
▲ chillily
2楼-- · 2020-03-31 05:07

The basic concept revolves around this idea of a "delta" or "change" value. This value is then applied to the state you want to change by either incrementing or decrementing the state value by it.

Because of the nature of Swing, you can't block the Event Dispatching Thread, otherwise you end up preventing from processing incoming events (such as paint and key events).

Equally, you should never try and update any UI component (or state variable that might effect the UI) from any thread other then the EDT.

While there are tricks you can apply to facilitate these requirements, the simplest is to use a javax.swing.Timer, which triggers a actionPerformed event on a regular bases within the EDT.

When this occurs you "update" all the elements by the prescribed amount and repaint the screen.

enter image description hereenter image description here

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
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.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
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;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class PacManTest {

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

    public PacManTest() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new MazePane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class PacMan {

        private int x;
        private int y;

        private int deltaX;
        private int deltaY;

        private BufferedImage sprite;

        public PacMan() {
            try {
                sprite = ImageIO.read(new File("PacMan.png"));
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }

        public void move(int x, int y) {
            deltaX = x;
            deltaY = y;
        }

        public void update(MazePane pane) {
            x += deltaX;
            y += deltaY;
            if (x + sprite.getWidth() > pane.getWidth()) {
                x = pane.getWidth() - sprite.getWidth();
            } else if (x < 0) {
                x = 0;
            }
            if (y + sprite.getHeight() > pane.getHeight()) {
                y = pane.getHeight() - sprite.getHeight();
            } else if (y < 0) {
                y = 0;
            }
        }

        public void paint(MazePane pane, Graphics2D g2d) {
            Graphics2D g = (Graphics2D) g2d.create();

            float angle = 0;
            if (deltaX != 0) {
                angle = deltaX > 0 ? 0 : 180;
            } else if (deltaY != 0) {
                angle = deltaY > 0 ? 90 : 270;                
            }
            AffineTransform t = new AffineTransform();
            t.translate(x, y);
            t.rotate(Math.toRadians(angle), sprite.getWidth() / 2, sprite.getHeight() / 2);
            g.setTransform(t);
            g.drawImage(sprite, 0, 0, pane);
            g.dispose();
        }

    }

    public class MazePane extends JPanel {

        private PacMan pacMan;
        public MazePane() {
            pacMan = new PacMan();
            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    pacMan.update(MazePane.this);
                    repaint();
                }
            });
            timer.start();
            InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap am = getActionMap();

            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "left");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "right");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "up");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "down");

            am.put("left", new MoveAction(pacMan, -4, 0));
            am.put("right", new MoveAction(pacMan, 4, 0));
            am.put("up", new MoveAction(pacMan, 0, -4));
            am.put("down", new MoveAction(pacMan, 0, 4));
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            pacMan.paint(this, g2d);
            g2d.dispose();
        }

        public class MoveAction extends AbstractAction {

            private int deltaX;
            private int deltaY;
            private PacMan pacMan;

            public MoveAction(PacMan pacMan, int deltaX, int deltaY) {
                this.deltaX = deltaX;
                this.deltaY = deltaY;
                this.pacMan = pacMan;
            }

            @Override
            public void actionPerformed(ActionEvent e) {
                pacMan.move(deltaX, deltaY);
            }

        }        
    }
}

I would also recommend that you take the time to learn about Key Bindings, KeyListener suffer from focus issues, which key bindings are capable of addressing...

查看更多
太酷不给撩
3楼-- · 2020-03-31 05:28

You need to process the move in a separate thread. I.e.:

public class Pacman implements Runnable
{
    public void run(){
        //moving code, i.e. in a while loop

        //every move will notify the EDT:
        SwingUtilities.invokeLater(new Runnable(){
            public void run(){
                //update the Swing here - i.e. move Pacman
            }
        }
    }

    public void startMoving(){
        new Thread(this).start();
    }

    //more methods to set speed, direction, etc...
}

Then you keep a reference to an instance of Pacman class in your Gui class and respond to various key presses by changing pacman's parameters:

public void keyPressed(KeyEvent e){
    int keyCode = e.getKeyCode();
    if(keyCode == e.VK_LEFT){
        pacman.newDirection(LEFT); //for exmaple, enum with direction LEFT, RIGHT, UP, DOWN...
    }
    //etc... more logic
}
查看更多
登录 后发表回答