user9440134
user9440134

Reputation:

How to create a usable KeyReleased method in java

I'm new to swing and I am trying to make the square move but only when the key is released (ex W) but when i hold down the key the square just moves

KeyListener Class

I want to make sure that a key was pressed and if it is still pressed it should return false but true if it was pressed then released package javaGD.GameAssistant.Input;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

public class KeyManager implements KeyListener {
private boolean[] keys;

public KeyManager() {
    keys = new boolean[256];

}

@Override
public void keyPressed(KeyEvent e) {
    keys[e.getKeyCode()] = true;

}

@Override
public void keyReleased(KeyEvent e) {
    keys[e.getKeyCode()] = false;

}

@Override
public void keyTyped(KeyEvent e) {

}

public boolean KeyPressed(int keycode) {
    return keys[keycode];

}

public boolean KeyReleased(int keycode) {
   return keys[keycode];
 }
}

Class where the square should move. KeyListener is inherited from GameAssistant(JFrame is created with the KeyListener)

package TestCode;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;

import javaGD.GameAssistant.GameAssistant;

public class Game extends GameAssistant {
public int x, xSpeed, y, ySpeed;

public Game(String title, int width, int height, boolean makeResizable) {
    super(title, width, height, makeResizable);

}

@Override
public void setup() {
    x = 0;
    y = 0;
    xSpeed = 5;
    ySpeed = 5;
}

@Override
public void update() {
    if (this.Keyboard().KeyReleased(KeyEvent.VK_D)) {
        x += xSpeed;
    }

    if (this.Keyboard().KeyReleased(KeyEvent.VK_A)) {
        x -= xSpeed;
    }

    if (this.Keyboard().KeyReleased(KeyEvent.VK_W)) {
        y -= ySpeed;
    }

    if (this.Keyboard().KeyReleased(KeyEvent.VK_S)) {
        y += ySpeed;
    }
}

@Override
public void draw(Graphics g) {

    g.setColor(Color.BLACK);
    g.fillRect(x, y, 20, 20);

}

}

Upvotes: 1

Views: 6246

Answers (1)

MadProgrammer
MadProgrammer

Reputation: 347314

KeyReleased should be returning !keys[keycode] Otherwise it will return false when released and true when pressed

public boolean KeyReleased(int keycode) {
   return !keys[keycode];
}

I would also recommend using the Key bindings API over KeyListener as it's more reliable and re-usable.

If you are only planning on a limited number of input operations, I would use Set and a enum, this way you can decouple the "how" from the "what".

You update method doesn't care "how" the inputs are managed, only the "what is the state"

Conceptually, maybe something like...

public enum GameInput {
    UP, DOWN, LEFT, RIGHT;
}

public class KeyManager implements KeyListener {

    private Set<GameInput> inputs = new HashSet<>();

    public KeyManager() {
    }

    @Override
    public void keyPressed(KeyEvent e) {
        // Check the key code, verify if it's one of the configured
        // actions keys
        // The key code could come from a configuration file which might
        // be customisable by the user...
        if (e.getKeyCode() == KeyEvent.VK_W) {
            inputs.add(GameInput.UP);
        } else if (e.getKeyCode() == KeyEvent.VK_S) {
            // etc...
        } // etc...
    }

    @Override
    public void keyReleased(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_W) {
            inputs.remove(GameInput.UP);
        } else if (e.getKeyCode() == KeyEvent.VK_S) {
            // etc...
        } // etc...
    }

    @Override
    public void keyTyped(KeyEvent e) {

    }

    public boolean isKeyPressed(GameInput input) {
        return inputs.contains(input);

    }

    public boolean isKeyReleased(GameInput input) {
        return !isKeyPressed(input);
    }
}

And your update method might look like...

@Override
public void update() {
    if (this.Keyboard().isKeyReleased(GameInput.RIGHT)) {
        x += xSpeed;
    }

    if (this.Keyboard().isKeyReleased(GameInput.LEFT)) {
        x -= xSpeed;
    }

    if (this.Keyboard().isKeyReleased(GameInput.UP)) {
        y -= ySpeed;
    }

    if (this.Keyboard().isKeyReleased(GameInput.DOWN)) {
        y += ySpeed;
    }
}

Now your update method doesn't care "how" the inputs are generated, only what to do when they are (or aren't) set.

Personally, I'd use a InputManager class and further decouple it, so inputs could be generate via other means, like buttons, mouse inputs, game pads, etc...

Runnable example...

Conceptually, this should work. I say "conceptually", because while I was testing I came across a number of "weird" issues that I can't contribute to either Java (1.8) or MacOS - or because they are a combination of both...more details below...

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.Set;
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;

public class Test {

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

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private Box box = new Box();

        public TestPane() {
            InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap am = getActionMap();

            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "Up.pressed");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Up.released");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "Down.pressed");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Down.released");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Left.pressed");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Left.released");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Right.pressed");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Right.released");

            am.put("Up.pressed", new KeyAction(InputAction.UP, true));
            am.put("Up.released", new KeyAction(InputAction.UP, false));
            am.put("Down.pressed", new KeyAction(InputAction.DOWN, true));
            am.put("Down.released", new KeyAction(InputAction.DOWN, false));
            am.put("Left.pressed", new KeyAction(InputAction.LEFT, true));
            am.put("Left.released", new KeyAction(InputAction.LEFT, false));
            am.put("Right.pressed", new KeyAction(InputAction.RIGHT, true));
            am.put("Right.released", new KeyAction(InputAction.RIGHT, false));

            Timer timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    box.update(getBounds());
                    repaint();
                }
            });
            timer.start();
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            box.draw(g);
        }

    }

    public class Box {

        public int x, xSpeed, y, ySpeed, width, height;

        public Box() {
            x = 0;
            y = 0;
            xSpeed = 1;
            ySpeed = 1;
            width = 10;
            height = 10;
        }

        public void update(Rectangle bounds) {
            if (!InputManager.INSTANCE.isSet(InputAction.LEFT)) {
                x -= xSpeed;
            }
            if (!InputManager.INSTANCE.isSet(InputAction.RIGHT)) {
                x += xSpeed;
            }
            if (InputManager.INSTANCE.isSet(InputAction.UP) && InputManager.INSTANCE.isSet(InputAction.DOWN)) {
                //
            } else if (!InputManager.INSTANCE.isSet(InputAction.UP)) {
                y -= ySpeed;
            } else if (!InputManager.INSTANCE.isSet(InputAction.DOWN)) {
                y += ySpeed;
            }

            if (x < bounds.x) {
                x = 0;
            } else if (x + width > (bounds.x + bounds.width)) {
                x = bounds.x + (bounds.width - width);
            }
            if (y < bounds.y) {
                y = 0;
            } else if (y + height > (bounds.y + bounds.height)) {
                y = bounds.y + (bounds.height - height);
            }
        }

        public void draw(Graphics g) {
            g.setColor(Color.BLACK);
            g.fillRect(x, y, width, height);
        }
    }

    public enum InputAction {
        UP, DOWN, LEFT, RIGHT;
    }

    public enum InputManager {
        INSTANCE;

        private Set<InputAction> actions = new HashSet<>();

        public void set(InputAction action) {
            actions.add(action);
        }

        public void remove(InputAction action) {
            actions.remove(action);
        }

        public boolean isSet(InputAction action) {
            return actions.contains(action);
        }
    }

    public class KeyAction extends AbstractAction {

        private InputAction action;
        private boolean apply;

        public KeyAction(InputAction action, boolean apply) {
            this.action = action;
            this.apply = apply;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("!");
            if (apply) {
                System.out.println("Apply " + action);
                InputManager.INSTANCE.set(action);
            } else {
                System.out.println("Remove " + action);
                InputManager.INSTANCE.remove(action);
            }
        }
    }
}

TL;DR

While testing the above example, I came across a bizarre number of issues, I've not seen before (not that I've been doing this kind of thing recently).

When using KeyListener, if I pressed (and held) two buttons, I could see the "pressed" action, but there was no repeating events, which is something I would normally expect (for any keys). When I released on key, I saw the "release" action, but when I pressed (and held it), no new "press" action was generated.

I tried the key bindings API (as demonstrated above) and still had no success (similar results).

I then attached a AWTEventListener directly to the event queue and monitored ALL the key strokes.

I noted that, some times (even is just tapping a key repeatedly) that "pressed" might not be generated.

I also noted that holding one or more keys down, releasing and pressing a key again, more often then not, did not generate a new press event (only release events)

I'm using macOS 10.13.6 and Java 1.8.0_144-b01 - it could be bug in either or both, but I don't have the means to test it otherwise

Update...

So, after updating from Java 1.8 to Java 1.10, the above mentioned issue seems to be resoled - however, this highlights another, hardware issue, where only a certain number of keys can be actively pressed at a time - See How do I remove the limit on PC keyboard button presses? for more details

Upvotes: 1

Related Questions