How can I set up my keybinds in such a way that the character does not pause between inputs?

I'm attempting to make a simple bullet hell game and am struggling to make smooth movement mechanics for the character. Here is my current code:

package bullethell;

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
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.border.EmptyBorder;

public class GameDisplay extends JFrame {

    private static final long serialVersionUID = -2586882806098087146L;
    private JPanel contentPane;
    private GameObject player;
    
    int xDirection = 0;
    int yDirection = 0;
    
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    GameDisplay frame = new GameDisplay();
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public GameDisplay() throws IOException {
        setUndecorated(true);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        contentPane.setLayout(new BorderLayout(0, 0));
        setContentPane(contentPane);
        
        setMovementKeys();
        
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(0, 
                  0, 
                  (int) 500, 
                  (int) 500);
        
        Image sprite = ImageIO.read(new File("Sprites/Player.png"));
        player = new GameObject(sprite);
        player.setBounds(0, 0, sprite.getWidth(null), sprite.getHeight(null));
        contentPane.add(player);
    }

    private void setMovementKeys() {
        
        InputMap in = contentPane.getInputMap();
        ActionMap out = contentPane.getActionMap();
        
        in.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "w-pressed");
        in.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "w-released");
        in.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "a-pressed");
        in.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "a-released");
        in.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "s-pressed");
        in.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "s-released");
        in.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "d-pressed");
        in.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "d-released");
        
        out.put("w-pressed", new MovementKeyAction(0,-10));
        out.put("w-released", new MovementKeyAction(0,0));
        out.put("a-pressed", new MovementKeyAction(-10,0));
        out.put("a-released", new MovementKeyAction(0,0));
        out.put ("s-pressed", new MovementKeyAction(0,10));
        out.put("s-released", new MovementKeyAction(0,0));
        out.put ("d-pressed", new MovementKeyAction(10,0));
        out.put("d-released", new MovementKeyAction(0,0));
        
        Timer timer = new Timer(1, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                player.setLocation(player.getX() + xDirection, player.getY() + yDirection);
            }
        });
        timer.start();
    }
    
    private class MovementKeyAction extends AbstractAction {
        
        private static final long serialVersionUID = -3671863065039719595L;
        public int x, y;
        
        public MovementKeyAction(int x, int y) {
            this.x = x;
            this.y = y;
        }
        
        @Override
        public void actionPerformed(ActionEvent e) {
            xDirection = x;
            yDirection = y;
        }
        
    }
    
}

What I am struggling with is modifying this so that there is no delay between inputs of different directions. Here is a gif of what I mean: https://i.stack.imgur.com/Xy6mT.gif

Any help would be greatly appreciated.

1 answer

  • answered 2022-05-07 03:31 MadProgrammer

    Instead of relying on the event state (press/release) directly, instead, update a "direction" state which can include both vertical and horizontal directions simultaneously and then use the "game loop" (ie the Timer) to make determinations about what to do based on that state.

    The problem you're trying to solve is a OS based one, there is a delay between the first and repeating key events, so, instead, you get the first key event and update some kind of state, this then allows the Timer to manage that state

    For example...

    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.util.Set;
    import java.util.TreeSet;
    import javax.swing.AbstractAction;
    import javax.swing.ActionMap;
    import javax.swing.InputMap;
    import javax.swing.JComponent;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.KeyStroke;
    import javax.swing.Timer;
    
    public class Main {
        public static void main(String[] args) {
            new Main();
        }
    
        public Main() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    JFrame frame = new JFrame();
                    frame.add(new GamePane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    //
    //    public class GameDisplay extends JFrame {
    //
    //        private static final long serialVersionUID = -2586882806098087146L;
    //        private JPanel contentPane;
    //        private Shape player;
    //
    //        int xDirection = 0;
    //        int yDirection = 0;
    //
    //        public GameDisplay() throws IOException {
    //            setUndecorated(true);
    //            contentPane = new JPanel() {
    //                @Override
    //                public Dimension getPreferredSize() {
    //                    return new Dimension(500, 500);
    //                }
    //            };
    //            contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
    //            contentPane.setLayout(new BorderLayout(0, 0));
    //            setContentPane(contentPane);
    //
    //            setMovementKeys();
    //
    //            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    //
    //            player = new Re
    //            
    //            player.setBounds(0, 0, sprite.getWidth(null), sprite.getHeight(null));
    //            contentPane.add(player);
    //        }
    //    }
    
        protected class GamePane extends JPanel {
            public enum Direction {
                UP, DOWN, LEFT, RIGHT;
            }
    
            private Set<Direction> directions = new TreeSet<>();
            private Player player = new Player();
    
            public GamePane() {
                setLayout(null);
                player.setBounds(0, 0, 10, 10);
                add(player);
                setMovementKeys();
                Timer timer = new Timer(5, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        int x = player.getX();
                        int y = player.getY();
                        if (directions.contains(Direction.UP)) {
                            y -= 1;
                        } else if (directions.contains(Direction.DOWN)) {
                            y += 1;
                        }
                        if (directions.contains(Direction.LEFT)) {
                            x -= 1;
                        } else if (directions.contains(Direction.RIGHT)) {
                            x += 1;
                        }
    
                        if (y < 0) {
                            y = 0;
                        } else if (y + player.getHeight() > getHeight()) {
                            y = getHeight() - player.getHeight();
                        }
                        if (x < 0) {
                            x = 0;
                        } else if (x + player.getWidth() > getWidth()) {
                            x = getWidth() - player.getWidth();
                        }
    
                        player.setLocation(x, y);
                    }
                });
                timer.start();
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(500, 500);
            }
    
            private void setMovementKeys() {
                InputMap in = getInputMap();
                ActionMap out = getActionMap();
    
                in.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "w-pressed");
                in.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "w-released");
                in.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "a-pressed");
                in.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "a-released");
                in.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "s-pressed");
                in.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "s-released");
                in.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "d-pressed");
                in.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "d-released");
    
                out.put("w-pressed", new MovementKeyAction(Direction.UP, true));
                out.put("w-released", new MovementKeyAction(Direction.UP, false));
                out.put("a-pressed", new MovementKeyAction(Direction.LEFT, true));
                out.put("a-released", new MovementKeyAction(Direction.LEFT, false));
                out.put("s-pressed", new MovementKeyAction(Direction.DOWN, true));
                out.put("s-released", new MovementKeyAction(Direction.DOWN, false));
                out.put("d-pressed", new MovementKeyAction(Direction.RIGHT, true));
                out.put("d-released", new MovementKeyAction(Direction.RIGHT, false));
            }
    
            protected void didActivate(Direction direction) {
                directions.add(direction);
            }
    
            protected void didDeactivate(Direction direction) {
                directions.remove(direction);
            }
    
            private class MovementKeyAction extends AbstractAction {
    
                private Direction direction;
                private boolean active;
    
                public MovementKeyAction(Direction direction, boolean active) {
                    this.direction = direction;
                    this.active = active;
                }
    
                @Override
                public void actionPerformed(ActionEvent e) {
                    System.out.println(direction + "/" + active);
                    if (active) {
                        didActivate(direction);
                    } else {
                        didDeactivate(direction);
                    }
                }
    
            }
        }
    
        protected class Player extends JComponent {
            @Override
            protected void paintComponent(Graphics g) {
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.fillOval(0, 0, getWidth() - 1, getHeight() - 1);
                g2d.dispose();
            }
        }
    }
    

How many English words
do you know?
Test your English vocabulary size, and measure
how many words do you know
Online Test
Powered by Examplum