Search code examples
javakeypresskeylistenerjapplet

Key Ghosting Problems


I am making a game and I ran into a key ghosting problem (where the program only detects one keypress at a time, so the player can't go diagonally). I was watching this tutorial: https://www.youtube.com/watch?v=5UaEUrbpDPE I followed everything they said and it still only detects one key at a time.

Main:

public class Main extends JApplet implements Runnable, KeyListener {
    private static final long serialVersionUID = 1L;
    public static int width = 900;
    public static int height = 600;
    public static int fps = 60;
    public static Main instance;

    public static Ailoid ailoid = new Ailoid();
    public static Player player = new Player();

    // Initialize
    public void init() {
        setSize(width, height);
        setBackground(Color.white);
        addKeyListener(this);
        setFocusable(true);
        setFocusTraversalKeysEnabled(false);
        requestFocus();
        instance = this;

        ailoid.setLocation(new Location(100, 100));
        AlienManager.registerAlien(ailoid);
        player.setLocation(new Location(400, 400));
    }

    // Paint graphics
    public void paint(Graphics g) {
        super.paint(g);
        paintComponent(g);
    }
    public void paintComponent(Graphics g) {
        for (Alien alien : AlienManager.getAliens()) {
            Location loc = alien.getLocation();
            g.setColor(Color.GREEN);
            g.fillRect(loc.getX(), loc.getY(), 10, 25);
        }

        g.setColor(Color.BLUE);
        Location loc = Main.player.getLocation();
        g.fillRect(loc.getX(), loc.getY(), 10, 25);
    }

    // Thread start
    @Override
    public void start() {
        Thread thread = new Thread(this);
        thread.start();
    }
    // Thread stop
    @Override
    public void destroy() {

    }

    // Thread run
    @Override
    public void run() {
        Thread thread = new Thread(this);
        while (thread != null) {
            Updater.run();
            repaint();
            try {
                // 1000 divided by fps to get frames per second
                Thread.sleep(1000 / fps);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void keyPressed(KeyEvent evt) {
        if (!KeysDown.get().contains(evt.getKeyCode()))
            KeysDown.add(new Integer(evt.getKeyCode()));
        KeyPress.run(evt);
    }

    @Override
    public void keyReleased(KeyEvent evt) {
        KeysDown.remove(new Integer(evt.getKeyCode()));
    }

    @Override
    public void keyTyped(KeyEvent evt) {

    }
}

KeysDown:

public class KeysDown {
    private static ArrayList<Integer> keysDown = new ArrayList<Integer>();

    public static ArrayList<Integer> get() {
        return keysDown;
    }
    public static void add(Integer key) {
        keysDown.add(key);
    }
    public static void remove(Integer key) {
        keysDown.remove(key);
    }
}

KeyPress:

public class KeyPress {
    public static void run(KeyEvent evt) {
        if (KeysDown.get().contains(KeyEvent.VK_RIGHT)) {
            Main.player.moveRight();
        }
        else if (KeysDown.get().contains(KeyEvent.VK_LEFT)) {
            Main.player.moveLeft();
        }
        else if (KeysDown.get().contains(KeyEvent.VK_DOWN)) {
            Main.player.moveDown();
        }
        else if (KeysDown.get().contains(KeyEvent.VK_UP)) {
            Main.player.moveUp();
        }
    }
}

Thank you!


Solution

  • Again as I have mentioned in previous comments:

    • Don't draw directly on the JApplet or in any top-level window.
    • If you give your applet a paintComponent method, it won't override any applet methods and won't gain the benefit of double buffering.
    • Instead draw in the paintComponent method of a JPanel, an thereby gain the benefit of double buffering.
    • Use Key Bindings not a KeyListener.
    • Also, I like to use a Swing Timer for simple game loops.
    • In the code below, I use an enum called Direction to encapsulate the idea of directions on the screen.
    • I then put the for Directions together with Boolean.FALSE in a Map<Direction, Boolean> called dirMap.
    • I run a Swing Timer continuously, polling the state of this Map and move a sprite based on the state of Booleans held by the dirMap.
    • I change the state of the map held values in my Key Binding Actions. So if the down key is pressed, its action will change the Direction.DOWN associated value in the Map to true, and when released, the Direction.DOWN associated value will be changed back to false.

    For example:

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Font;
    import java.awt.FontMetrics;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.RenderingHints;
    import java.awt.event.*;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.util.EnumMap;
    
    import javax.imageio.ImageIO;
    import javax.swing.*;
    
    @SuppressWarnings("serial")
    public class AnimateExample extends JPanel {
       public static final String DUKE_IMG_PATH = // https://duke.kenai.com/iconSized/duke.gif
             "https://duke.kenai.com/iconSized/duke4.gif";
       private static final int PREF_W = 800;
       private static final int PREF_H = 800;
       private static final int TIMER_DELAY = 20;
       private static final String KEY_DOWN = "key down";
       private static final String KEY_RELEASE = "key release";
       public static final int TRANSLATE_SCALE = 3;
       private static final String BACKGROUND_STRING = "Use Arrow Keys to Move Image";
       private static final Font BG_STRING_FONT = new Font(Font.SANS_SERIF,
             Font.BOLD, 32);
       private EnumMap<Direction, Boolean> dirMap = 
             new EnumMap<AnimateExample.Direction, Boolean>(Direction.class);
       private BufferedImage image = null;
       private int imgX = 0;
       private int imgY = 0;
       private int bgStringX; 
       private int bgStringY; 
    
       public AnimateExample() {
          for (Direction dir : Direction.values()) {
             dirMap.put(dir, Boolean.FALSE);
          }
          try {
             URL imgUrl = new URL(DUKE_IMG_PATH);
             image = ImageIO.read(imgUrl);
          } catch (MalformedURLException e) {
             e.printStackTrace();
          } catch (IOException e) {
             e.printStackTrace();
          }
    
          new Timer(TIMER_DELAY, new TimerListener()).start();
    
          // here we set up our key bindings
          int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
          InputMap inputMap = getInputMap(condition);
          ActionMap actionMap = getActionMap();
          for (final Direction dir : Direction.values()) {
    
             // for the key down key stroke
             KeyStroke keyStroke = KeyStroke.getKeyStroke(dir.getKeyCode(), 0,
                   false);
             inputMap.put(keyStroke, dir.name() + KEY_DOWN);
             actionMap.put(dir.name() + KEY_DOWN, new AbstractAction() {
    
                @Override
                public void actionPerformed(ActionEvent arg0) {
                   dirMap.put(dir, true);
                }
             });
    
             // for the key release key stroke
             keyStroke = KeyStroke.getKeyStroke(dir.getKeyCode(), 0, true);
             inputMap.put(keyStroke, dir.name() + KEY_RELEASE);
             actionMap.put(dir.name() + KEY_RELEASE, new AbstractAction() {
    
                @Override
                public void actionPerformed(ActionEvent arg0) {
                   dirMap.put(dir, false);
                }
             });
          }
    
          FontMetrics fontMetrics = getFontMetrics(BG_STRING_FONT);
          int w = fontMetrics.stringWidth(BACKGROUND_STRING);
          int h = fontMetrics.getHeight();
    
          bgStringX = (PREF_W - w) / 2;
          bgStringY = (PREF_H - h) / 2;
       }
    
       @Override
       public Dimension getPreferredSize() {
          return new Dimension(PREF_W, PREF_H);
       }
    
       @Override
       protected void paintComponent(Graphics g) {
          super.paintComponent(g);
          Graphics2D g2 = (Graphics2D) g;
          g.setFont(BG_STRING_FONT);
          g.setColor(Color.LIGHT_GRAY);
          g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
          g.drawString(BACKGROUND_STRING, bgStringX, bgStringY);
    
          if (image != null) {
             g.drawImage(image, imgX, imgY, this);
          }
       }
    
       private class TimerListener implements ActionListener {
          public void actionPerformed(java.awt.event.ActionEvent e) {
             for (Direction dir : Direction.values()) {
                if (dirMap.get(dir)) {
                   imgX += dir.getX() * TRANSLATE_SCALE;
                   imgY += dir.getY() * TRANSLATE_SCALE;
                }
             }
             repaint();
          };
       }
    
       enum Direction {
          Up(KeyEvent.VK_UP, 0, -1), Down(KeyEvent.VK_DOWN, 0, 1), Left(
                KeyEvent.VK_LEFT, -1, 0), Right(KeyEvent.VK_RIGHT, 1, 0);
    
          private int keyCode;
          private int x;
          private int y;
    
          private Direction(int keyCode, int x, int y) {
             this.keyCode = keyCode;
             this.x = x;
             this.y = y;
          }
    
          public int getKeyCode() {
             return keyCode;
          }
    
          public int getX() {
             return x;
          }
    
          public int getY() {
             return y;
          }
    
       }
    
       private static void createAndShowGui() {
          AnimateExample mainPanel = new AnimateExample();
    
          JFrame frame = new JFrame("Animate Example");
          frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
          frame.getContentPane().add(mainPanel);
          frame.pack();
          frame.setLocationByPlatform(true);
          frame.setVisible(true);
       }
    
       public static void main(String[] args) {
          SwingUtilities.invokeLater(new Runnable() {
             public void run() {
                createAndShowGui();
             }
          });
       }
    }
    

    For more on Key Bindings, please check out the informative tutorial which you can find here.