1

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.

Machavity
  • 30,841
  • 27
  • 92
  • 100
Alex Jones
  • 226
  • 3
  • 13
  • 1
    How is the `while` loop suppose to exit? If you're using Swing, this method is now block the Event Dispatching Thread, meaning that no new key events can be passed to your application... – MadProgrammer Jun 04 '13 at 07:09
  • I am using Swing. That explains why this isn't working, but without a while loop, I don't see how I could keep the movement going. – Alex Jones Jun 04 '13 at 07:13
  • Normally you would have some kind timer event that allowed you to update the state of the UI at regular intervals. Take a look at [this example](http://stackoverflow.com/questions/16290543/using-keypad-to-move-a-circle-at-angles-in-java/16290659#16290659) – MadProgrammer Jun 04 '13 at 07:20
  • To be honest, I've looked up and down the code many times and I can't see any timer that's updating the UI... Maybe I'm just not looking in the right place... – Alex Jones Jun 04 '13 at 07:40
  • Sorry, my bad, I write so many of these type of examples. [This](http://stackoverflow.com/questions/16622630/gradually-speeding-a-sprite/16623202#16623202) one changes the speed of the object each time you press the left or right key, but the concept is the same (change direction) – MadProgrammer Jun 04 '13 at 07:43

2 Answers2

1

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...

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
0

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
}
Jakub Zaverka
  • 8,816
  • 3
  • 32
  • 48
  • Right now, I only have one class, my GUI class... Is it necessary to have two separate classes? And what exactly is the purpose of the pacman class you're using? – Alex Jones Jun 04 '13 at 08:04
  • @AlexanderJ93 No it is not necessary, only it is common practice to separate logic and graphical user interface. The Pacman class separates logic (the pacman) from the graphical user interface (GUI class). – Jakub Zaverka Jun 04 '13 at 08:17