0
\$\begingroup\$

I'm making an inventory system for my pure Java game right now. Currently, the game runs at a 60UPS, and inputs are checked each update.

I have inputs set up using arrays of the current update and last update's key presses:

private final int NUM_KEYS = 256;
    
    private boolean[] pressedKeys;
    private boolean[] pressedKeysLast;

    // yes, those are initialized in the actual code :)

    @Override
    public void keyPressed(KeyEvent e) {
        
        if (e.getKeyCode() <= NUM_KEYS) {
            // Presses the key if it is valid.
            pressedKeys[e.getKeyCode()] = true;
        }
        
    }

    /**
     * Handles the keyReleased event.
     */
    
    @Override
    public void keyReleased(KeyEvent e) {
        
        if (e.getKeyCode() <= NUM_KEYS) {
            // Releases the key if it is valid.
            pressedKeys[e.getKeyCode()] = false;
        }
    }

    /**
     * Updates the input class and updates the input arrays.
     */
    
    public void update() {
        
        /*
         * Sets the values of last frame's pressed keys
         * to this frame's pressed keys.
         */
        for (int index = 0; index < NUM_KEYS; index++) {
            pressedKeysLast[index] = pressedKeys[index];
        }
    }

    /**
     * Determines if a specific key has just been pressed.
     * @param keyCode The key to be checked.
     * @return True if the key is pressed, false if it is not.
     */
    
    public boolean isKeyDown(int keyCode) {
        /*
         * This will return true if the key has been pressed in
         * this frame, but not in the last frame.
         */
        return pressedKeys[keyCode] && !pressedKeysLast[keyCode];
    }

This works fine, but for logging quick keypresses, such quickly pressing and releasing "E" to open the player's inventory, this system is too fast.

I have seen that every time that I try to quickly press and release a key to see if I can open the inventory using isKeyDown(KeyEvent.VK_E), the update() method is called approximately 3 times, and since the arrays reset each frame, I only have a 0.05 seccond window to achieve the result I want, which is to get when the key is pressed and released quickly.

I am looking for suggestions to my problem so theinput.isKeyDown(int keycode) method be able to determine when keycodes are quickly pressed, regardless if the duration of the press is 0.05s or 1.5s.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

To keep track of when keys are down and when a key has been pressed (down and up) you need to track some additional data and states.

Key states

You will need a frame counter that counts up for each call to update and 3 arrays to hold the key states.

  • downKeys boolean array true when key is down

  • wasPressedKeys boolean array true after a key has been pressed

  • keyHeldTime int array holds when key went down, in frames, if the key is down.

    When the key is up it holds how long the key was down for in frames.

The key event methods flag when keys are down, and sets the frame time or held length for each key (see example keyPressed and keyReleased)

Query key states

Then to query the keyboard state we have two methods

  • int wasKeyDown(int keyCode) that returns how long the key was down for in frames.

    If the key is currently down it returns 0.

  • int isKeyDown(int keyCode) Return number of frames the key is down for.

    If the key is up then it returns 0

To ensure we react only once to key events an additional method is used to clear was pressed state

  • void clearPressedKey(int keyCode)

Using key states

We can now react to both keys held, and key pressed. If a key has both a fast action and a hold action, the hold action must be delayed (or reversible) until the key has been held for longer than the fast action time.

In the example the key 13 if pressed for less than 4 frames will call the method doActionShort(). it will also reset the wasPressedKeys flag by calling clearPressedKey(13) to ensure we don't repeat the action.

If the key is held nothing will happen until it has been held for more than 3 frames. After being held for over 3 frames it will call the method doActionHold() as long as the key is held down.

Note that the first thing the update function does is increment the frameCounter so that you can track when and how long keys have been down for.

Note that you do not need to count frames, you can use time rather than frames.

Example code snippets

/* Keyboard State */
private boolean[] downKeys;
private boolean[] wasPressedKeys;
private int[] keyHeldTimes;
private int frameCounter = 0;

/* Keyboard events */
@Override
public void keyPressed(KeyEvent e) {
    int keyCode = e.getKeyCode();
    downKeys[keyCode] = true;
    wasPressedKeys[keyCode] = false;
    keyHeldTime[keyCode] = frameCounter;
    
}
@Override
public void keyReleased(KeyEvent e) {
    int keyCode = e.getKeyCode();
    downKeys[keyCode] = false;
    wasPressedKeys[keyCode] = true;
    keyHeldTime[keyCode] = frameCounter - keyHeldTime[keyCode];
}

/* Keyboard state queries */
public int isKeyDown(int keyCode) {
    if (downKeys[keyCode] && !wasPressedKeys[e.keyCode]) {
        return keyHeldTime[keyCode] - frameCounter;
    }
    return 0;
}
public int wasKeyDown(int keyCode) {
    if (!downKeys[keyCode] && wasPressedKeys[e.keyCode]) {
        return keyHeldTime[keyCode];
    }
    return 0;
}

/* Keyboard state utility */
public void clearPressedKey(int keyCode) { 
    wasPressedKeys[keyCode] = false;
}

/* Update loop ? 60FPS */
public void update() {
    frameCounter += 1; // count current frame
    
    int wasDownFor = wasKeyDown(13);        // was key down and for how long
    if (wasDownFor > 0 && wasDownFor < 4) { // key was down for less than 4 frames            
        clearPressedKey(13);                // reset the key
        doActionShort();                    // do short action
    }
    
    if (isKeyDown(13) > 3) { // key is down for more than 3 frames            
        doActionHold();      // while key is held do hold action
    }
}

```
\$\endgroup\$
3
  • \$\begingroup\$ This looks amazing! However, my only concern is the use of frameCounter += 1 which never seems to be reset. I take it that that would be how many frames (or whatever I track) have elapsed since the game loop began. I know Java's max integer value is very high, but is there a chance I could exceed this limit? If so, what other approach should I use? \$\endgroup\$
    – ElliottV4
    Jun 7, 2021 at 19:03
  • 1
    \$\begingroup\$ @ElliottV4 Java's int is a signed 32bit integer with max value 2 ^ 31 -1 which would give you over a year of continuous run time before cycling, If that is an issue you can use a long (signed 64 bit int) which will give you ~50 million years of run time before cycling. \$\endgroup\$
    – Blindman67
    Jun 8, 2021 at 8:07
  • \$\begingroup\$ Yeah, I tested everything that you recommended and with a few tweaks, it all works fine. About the max value, I’m sure that it won’t be a problem if I just let the application crash after a straight year of letting it run lol. \$\endgroup\$
    – ElliottV4
    Jun 8, 2021 at 10:52

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .