[solved] Capturing - arrow keys and letters

Hi guys,

I’m having a bit of difficulty right now working out how to capture arrow keys in my game…

Using Java 2d, I have a Canvas object in my JPanel that has the listeners attached to it:

canvas = new Canvas();

//  add keyboard and mouse handlers
canvas.addKeyListener((KeyListener)engine.getEventHandler());
canvas.addMouseListener((MouseListener)engine.getEventHandler());
canvas.addMouseMotionListener((MouseMotionListener)engine.getEventHandler());

This all works lovely, in my EventHandler class I handle each event and do the actions required:

public void keyPressed(KeyEvent e) {
			
switch (e.getKeyCode()) {
			
case Constants.KEY_MAIN_MENU:
	//  show the main menu
	break;
case Constants.KEY_TOGGLE_GRID:
case Constants.KEY_TOGGLE_HITBOXES:
	//  pass to Renderer to process
	renderer.processKeyDownInput(e.getKeyCode());
	break;
				
case Constants.KEY_PLAYER_ATTACK:
	//  pass to Controller
	controller.doPlayerMeleeAttack();
	break;
case Constants.KEY_PLAYER_MOVE_UP:
	controller.addMovementKeyFlag(Constants.DIRECTION_UP);
	break;
case Constants.KEY_PLAYER_MOVE_RIGHT:
	controller.addMovementKeyFlag(Constants.DIRECTION_RIGHT);
	break;
case Constants.KEY_PLAYER_MOVE_DOWN:
	controller.addMovementKeyFlag(Constants.DIRECTION_DOWN);
	break;
case Constants.KEY_PLAYER_MOVE_LEFT:
	controller.addMovementKeyFlag(Constants.DIRECTION_LEFT);
	break;
			
}
			
}

The addMovementKey method in my Controller maintains a stack of keys held down (so if the user is holding the “down” movement key and presses the “right” movement key for a second the player will move right for as long as that key is held then revert to downward movement afterwards:

public void addMovementKeyFlag(byte direction) {
//  first, check if key is already being held down
//  this should never happen but good to check anyway
int i;
		
for (i=0; i<this.movementKeys.size(); i++) {
			
  if (this.movementKeys.get(i) == direction) {
    // check that we aren't trying to trigger the same key twice
    System.out.println("Duplicate direction key found! :: "+direction);
    return;
  }
			
}
		
this.movementKeys.add((int)direction);
this.playerEntity.setFacingDirection(direction);
this.playerEntity.setState(EntityState.MOVING);
		
}

My problem has now started because someone else tried to play my game and immediately asked “why can’t I use the arrow keys?”. So now I’m on a journey to get the arrow keys working.

The problem specifically is that when holding down a character on the keyboard (e.g. W, A, S, or D), the keyPressed event fires exactly once (meaning that to continually travel in a direction we have to flag the key as being held down then un-flag it when the key is released). However, when an arrow key is pressed (VK_UP, VK_DOWN, as the keyCode returns) the event fires continuously.

Is there a way to get around that? Or can anyone suggest how I can maintain my clever little stack to track the other buttons being held down at the same time?

Sorry if this is a massive ramble and doesn’t make any sense!

P.S. it does kind of work but it means the game is constantly looping over a list of up to 4 items checking if an item is already in the list (which doesn’t sound like a good thing to me).

Why are you storing your pressed keys in a stack? Why not use a Set, that way you don’t need to check for duplicates?

Alternatively, do you really care about every single key? Why not just have a separate variable for each key you care about, and then check against those in the game loop?

Hi Kev,

Thanks for your reply.

I just had a look at the Set documentation (never heard of one before). Looks like LinkedHashMap is the closed thing for this task, as it stores items in the order that they were added (so I can easily back-track through the list as and when keys are un-pressed).

I think the aim for my question is to find out if there’s a way to stop the arrow keys from duplicating their keypresses (or flip that over and make the W/A/S/D keys repeat theirs).

And you’re right, I don’t care about all the keys, just the 4 that control the player’s movement. The way it works right now is like this:

PLAYER_UP key pressed
  PLAYER_UP added to movementKeys
  player starts moving up
PLAYER_RIGHT key pressed (UP still pressed)
  PLAYER_RIGHT added to movementKeys
  player starts moving right
PLAYER_RIGHT key released
  PLAYER_RIGHT removed from movementKeys
  player reverts to previous direction (UP)

This works fine with WASD but when using arrows it goes a bit mad as, for example, PLAYER_UP fires continuously.

And with regard of storing the keys currently held, it’s the order of them that is important to provide this feature. Unless, of course, the feature I am trying to implement is a basic thing that would happen regardless if I used a different method of capturing the keys… :frowning:

I think the behavior of your arrow keys is going to depend on your keyboard and your system. In fact, I’ve never seen the behavior you’re describing: my arrow keys act just like my other keys.

And with my suggestion of simply keeping track of the variables separately, you wouldn’t really care about the keyPressed() function being called multiple times. Here is some pseudocode:


boolean upPressed = false;

void keyPressed(KeyEvent ke){
  if(ke.getKeyCode() == KeyEvent.VK_UP){
      upPressed = true;
   }
}

void keyReleased(KeyEvent ke){
  if(ke.getKeyCode() == KeyEvent.VK_UP){
      upPressed = false;
   }
}

void gameLoop(){
   if(upPressed){
      //up is pressed, go up
   }
}

Oh yeah, gotcha. That idea is great and I use it for tracking mouse clicks and drags.

That’s strange then, I wonder if it’s a “feature” of my keyboard or something lol. Perhaps more investigation is needed then :slight_smile:

It’s an OS thing. You’ll get different patterns of repeat/no repeat between Windows and Linux (can’t speak for OSX…never used it). Google has all sorts of information/workarounds if desired. KevinWorkman has suggested a good approach and it’s the same one I’d recommend. It’s tried and true and makes things consistent across the board. :slight_smile:

I’m running OSX so there we go :). I was very surprised when something as simple as changing from W to UP caused such a headache! I’m glad it happened now though as I could have quite happily coded my way through my game without even noticing this issue.

The problem with the workaround described by Kev is that I’m trying to keep track of the order in which the keys are pressed so when the user releases a key the character movement reverts back to the direction of the last-held-down key. In my universe there is no diagonal movement so holding both UP and LEFT won’t give you north-east movement so holding LEFT and tapping UP does a similar thing.

EDIT: Thank you for your insight Codehead :slight_smile:

In that case, then my only suggestion would be to use an ArrayList instead of a byte[] as the basis for your input stack. In your controller class you can simplify things a bit:


public void addMovementKeyFlag(byte direction) {
    //  first, check if key is already being held down
    //  this should never happen but good to check anyway        
    if(!this.movementKeys.contains(direction)) {
        this.movementKeys.add(0, direction);
        this.playerEntity.setFacingDirection(direction);
        this.playerEntity.setState(EntityState.MOVING);
    } 
}

You’re not going to be able to get around the checks, but it’s not a huge issue. The impact on performance should be minuscule assuming that you’re only capturing a few keys, 4 in your case, on the stack. :slight_smile:

Out of curiosity, how are you handling the removal of a key(s) from the stack? Say I was pressing left, then pressed up, then released left before releasing up. What would be the state of the input stack after finally releasing the up button?

Well there we go… I learnt something new today. I didn’t realise you could do ArrayList.contains() with a primitive, I thought it had to be a proper object! As it happens, my little stack is already an ArrayList so I’ll just update it in a bit to use .contains() instead looping over it :slight_smile:

This is my remove key method. If the removed key is in the middle of the stack then it’s removed, otherwise when that key finally got to the top of the stack then it would never be unpressed as that had trigger had already fired.

public void removeMovementKeyFlag(int direction) {
/* 
 * check for key already being held down, remove it if possible,
 * if most recent key is removed, revert to the previously held down key if there is one
*/
		
int i;
for (i=0; i<this.movementKeys.size(); i++) {
						
	if (this.movementKeys.get(i) == direction) {
				
		this.movementKeys.remove(i);
				
		if (this.movementKeys.size() > 0) {
					
			if (i == this.movementKeys.size()) {

				//  if the last item was popped off the stack then revert to the previous direction
				this.playerEntity.setFacingDirection(this.movementKeys.get(i-1).byteValue());
				break;
						
			}
					
		} else {

			//  if no more keys are pressed then the player should stop moving
			this.playerEntity.setState(EntityState.IDLE);
			break;
					
		}
				
	}
}

You were correct, you can’t (yet; we all await project valhalla), there is only ArrayList, not , and the method argument is autoboxed from byte to Byte and back.

Oops, my bad on that one. That’s what I get for trying to juggle too many things at once when I answered earlier. :point: Apologies.

Maybe an exercise for another time then, but I couldn’t work out how to use .contains() with Bytes instead of byte. I guess I’d need to write a comparator that used Byte.byteValue to compare the actual values as when I tried it like this:

Byte b = new Byte(3);
if (movementKeys.contains(b)) {
  //  blah...
}

It never contained the key.

Still, I have a working version just by looping over the items and ignoring duplicates anyway.

Thanks for your help and time guys :slight_smile:

You don’t need a Comparator. It should work fine.


import java.util.ArrayList;
import java.util.List;

public class Main{

	public static void main(String... args) {
		List<Byte> bytes = new ArrayList<Byte>();
		
		bytes.add((byte)1);
		bytes.add((byte)2);
		bytes.add((byte)3);
		bytes.add(new Byte((byte)4));
		
		for(byte b = 0; b <= 5; b++){
			System.out.println("Contains " + b + ": " + bytes.contains(b));
		}
	}
}

This code prints out:

Contains 0: false
Contains 1: true
Contains 2: true
Contains 3: true
Contains 4: true
Contains 5: false

Just make sure you’re actually checking against a byte and not an int!

Ohhh, that could have something to do with it then. There’s a strong possibility that I was checking against an int.

Thanks for the info Kevin :slight_smile: