How do I keep an object moving using arrow keys?

2019-09-05 04:41发布

I am making a snake game, and I want my snake to be moving continuously once a key is pressed. So, I press the down key, and it keeps moving even if the key is released. Right now, it just moves while the key is being held down.

    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_DOWN) {
           mySegment[0].moveSouth();
           repaint();
        }
    else if (e.getKeyCode() == KeyEvent.VK_UP) {
        mySegment[0].moveNorth();
        repaint();
    }
    else if(e.getKeyCode() == KeyEvent.VK_LEFT){
        mySegment[0].moveWest();
        repaint();
    }
    else if (e.getKeyCode() == KeyEvent.VK_RIGHT){
        mySegment[0].moveEast();
        repaint();
    }

    for (int a = 0; a < 10; a++) {
        if (myFruit[a].distance (mySegment[0].getX(), mySegment[0].getY())                
        <= 20) {
            myFruit[a].hide();
        }
    }

The "mySegment [0]" is the snake, and the "moveSouth" or whatever direction just moves it 5 pixels in that directin

2条回答
不美不萌又怎样
2楼-- · 2019-09-05 04:57

Use a "game loop" to drive the animation. Since this looks to be possibly a Swing or AWT GUI, then your best bet is to use a Swing Timer -- please check out the tutorial. The gist is that within the Timer's ActionListener you increment the position of the snake, changing its direction depending on the state of your key press.

I would use an enum to indicate Direction {UP, DOWN, LEFT, RIGHT}:

public enum Direction {
    UP,
    DOWN,
    LEFT,
    RIGHT
}

AND then a Map<Direction, Boolean> to indicate which direction to head:

private Map<Direction, Boolean> dirMap = new EnumMap<>(Direction.class);

Elsewhere you would initialize the Map to hold false in all values:

// initialize the map to all false
for (Direction dir : Direction.values()) {
    dirMap.put(dir, false);
}

Then change the state of the items in the Map within your code for listening to key presses and releases -- call map.put(Direction.UP, true) for instance when the up key is pressed, and likewise call map.put(Direction.UP, false) when it has been released, same for the other keys. Note that if yours is a Swing application, I'd use Key Bindings and not a KeyListener to do this. In the listener, I'd then call repaint() on the GUI.

Within the Swing Timer, iterate through the Map, setting the direction based on the state of the Map.

class fields:

private int spriteX = 0; // location of sprite
private int spriteY = 0;

private int directionX = 0; // direction sprite is heading
private int directionY = 0;

Within the ActionListener

// within the Swing Timer's ActionListener
if (dirMap.get(Direction.UP)) {
    directionY -= 1;
}
if (dirMap.get(Direction.DOWN)) {
    directionY += 1;
}
if (dirMap.get(Direction.RIGHT)) {
    directionX += 1;
}
if (dirMap.get(Direction.LEFT)) {
    directionY -= 1;
}

// here multiply directionX and directionY by some scale factor and use to place new snake head
// then call repaint();

For example (not a snake but a ball -- I'll leave the Snake to you)

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.EnumMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.swing.*;

@SuppressWarnings("serial")
public class DirTest extends JPanel {
    private static final int PREF_W = 800;
    private static final int PREF_H = PREF_W;
    private static final int TIMER_DELAY = 40;
    private static final Color SPRITE_COLOR = Color.RED;
    private static final int SPRITE_W = 20;
    private static final Color BG = Color.BLACK;
    public static final int SCALE = 1;
    private Map<Direction, Boolean> dirMap = new EnumMap<>(Direction.class);
    private int spriteX = 0;
    private int spriteY = 0;
    private int directionX = 0;
    private int directionY = 0;
    private Timer gameLoopTimer = new Timer(TIMER_DELAY, new TimerListener());

    public DirTest() {
        setKeyBindings();

        setBackground(BG);
        // initialize map to all 0;
        for (Direction dir : Direction.values()) {
            dirMap.put(dir, false);
        }

        gameLoopTimer.start();
    }

    private void setKeyBindings() {
        int condition = WHEN_IN_FOCUSED_WINDOW; // bind to keys if component in active window
        InputMap inputMap = getInputMap(condition);
        ActionMap actionMap = getActionMap();

        setKeyBinding(inputMap, actionMap, KeyEvent.VK_UP, Direction.UP);
        setKeyBinding(inputMap, actionMap, KeyEvent.VK_DOWN, Direction.DOWN);
        setKeyBinding(inputMap, actionMap, KeyEvent.VK_LEFT, Direction.LEFT);
        setKeyBinding(inputMap, actionMap, KeyEvent.VK_RIGHT, Direction.RIGHT);
    }

    private void setKeyBinding(InputMap inputMap, ActionMap actionMap, int keyCode, Direction dir) {
        KeyStroke press = KeyStroke.getKeyStroke(keyCode, 0, false);
        KeyStroke released = KeyStroke.getKeyStroke(keyCode, 0, true);

        Action pressAction = new PressedAction(dir, true);
        Action releasedAction = new PressedAction(dir, false);

        inputMap.put(press, press.toString());
        inputMap.put(released, released.toString());

        actionMap.put(press.toString(), pressAction);
        actionMap.put(released.toString(), releasedAction);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setColor(SPRITE_COLOR);
        g2.fillOval(spriteX, spriteY, SPRITE_W, SPRITE_W);
    }

    @Override
    public Dimension getPreferredSize() {
        if (isPreferredSizeSet()) {
            return super.getPreferredSize();
        }
        return new Dimension(PREF_W, PREF_H);
    }

    private class PressedAction extends AbstractAction {
        private boolean pressed;
        private Direction dir;

        public PressedAction(Direction dir, boolean pressed) {
            this.dir = dir;
            this.pressed = pressed;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            dirMap.put(dir, pressed);
        }
    }

    private class TimerListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            for (Entry<Direction, Boolean> entry : dirMap.entrySet()) {
                if (entry.getValue()) {
                    directionX += entry.getKey().getX();
                    directionY += entry.getKey().getY();
                }
            }

            spriteX += SCALE * directionX;
            spriteY += SCALE * directionY;

            repaint();
        }
    }

    private static void createAndShowGui() {
        JFrame frame = new JFrame("DirTest");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(new DirTest());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }
}

enum Direction {
    UP(0, -1),
    DOWN(0, 1),
    LEFT(-1, 0),
    RIGHT(1, 0);

    private int x;
    private int y;

    private Direction(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public int getX() {
        return x;
    }
    public int getY() {
        return y;
    }

}
查看更多
做自己的国王
3楼-- · 2019-09-05 04:57

If you want to keep the snake moving you need some kind of game-loop (as mentioned before). The easiest way is having a single field containing the current direction, and set it based on the input. So here are a few methods/classes you could use to set the correct direction/position

The Direction Enum

public enum  Direction
{
    NORTH, EAST, SOUTH, WEST;

    public Direction oposite()
    {
         switch(this)
         {
             case NORTH: return SOUTH;
             case SOUTH: return NORTH;
             case EAST: return WEST;
             case WEST: return EAST;
          }
    }
}

The method to set the current direction. (This assumes there is a field named 'currentDirection' which contains an enum (Direction) representing the current direction)

  public void setDirection(Direction newDirection)
 {
      if(currentDirection != newDirection.oposite())
          currentDirection = newDirection;
 }

This is somewhere in your game loop (either an timer, or a while loop including a 'sleep' call to prevent CPU hugging)

     switch(currentDirection)
     {
          case NORTH: mySegment[0].moveNorth(); break;
          case EAST: mySegment[0].moveEast(); break;
          case SOUTH: mySegment[0].moveSouth(); break;
          case WEST: mySegment[0].moveWest(); break;
     }
     repaint();

And of course instead of calling 'mySegment[0].moveNorth();' or someting equalivant in the actionHandlers for the keyEvents, you should only call 'setDirection();' to make the snake move.

I hope this helped you out.

查看更多
登录 后发表回答