Monday, March 24, 2008

Keyboard Input: Polling System In Java

When I first started game programming and well into about 4 years of it. I always wondered the reason for wanting to use a polling system for Input instead of an interrupt based method. Now I know why and I'll share with you.

I figured it was better to receive an interrupt event than constantly checking the state of every key on the keyboard. But in games its imperative to have our objects updating on a fixed framerate structure and order is primary. Thus if we were to use an interrupt based system it would destabilize when events occur. Sure it is possible to use Java's KeyListener interface for all our keyboard handling needs. But there is a bit more overhead than simply making a single listener that stores the state of each key on the keyboard for us to poll.

The main problem with KeyListener is it tempts us to write all our event handling code in the keyPressed, keyReleased, keyTyped methods which for games is very bad. Since it might take XX amount of milliseconds to execute our event handling code handling multiple events per frame won't do, usually you don't wanna be taking that much time handling your input anyways. The more important reason is that every KeyListener will run in a separate thread while handling the input which means you will have to handle any synchronization issues that might come up while developing.

Those are just a few reasons why you might want to use a keyboard polling system in a Java game.

Basic Start
So lets start with the basics of the idea. For our purposes we'll make it a singleton but if you have an existing design and can integrate it without using a singleton I would recommend it.
class InputManager implements KeyListener

     private static InputManager singleton = null;

     protected InputManager() {
     }

     public static InputManager getInstance() {
          if(singleton==null) {
               singleton = new InputManager();
          }
          return singleton;
     }
}
So now we need to add an array to save the keyboard state. I use two arrays of 256 boolean values. One array is for holding a single true false, down or not or up or not state.
private boolean[] key_state_up = new boolean[256];
private boolean[] key_state_down = new boolean[256];
You could just store a single integer or short array using different values for the keys state which would allow you more states but since there really aren't any other states boolean arrays will do just fine. So here you can see we have a single variable we can set for each of the 256 keys on the keyboard. This allows us to track multiple keys being pressed simultaneously, needed for fighting games and the like something we would have to do with boolean variables in each KeyListener implementor if we weren't using a single InputManager.

This allows the key state to remain fairly consistent during a single frame in the game update cycle and reset at the end of the cycle. So as long as our update is not taking forever and runs quickly the response time is pretty quick.

What more could you want?
So thats nice but what else, well we sometimes want to just check to see if the user pressed something. We don't necessarily care what it was just if they pressed something. So we add another couple bools for that.
private boolean keyPressed = false;
private boolean keyReleased = false;
So this allows us to make those nifty little pause screens or making sure we don't eat up the users CPU without making sure their using the game first. Usually you have something like "Press any key to continue" or whatever.

Getting Input
Now we need to actually make our InputManager instantiable by adding the KeyListener interface method implementations.
 public void keyPressed(KeyEvent e) {
  //System.out.println("InputManager: A key has been pressed code=" + e.getKeyCode());
  if( e.getKeyCode() >= 0 && e.getKeyCode() < 256 ) {
   keys[e.getKeyCode()] = (int) System.currentTimeMillis();
   key_state_down[e.getKeyCode()] = true;
   key_state_up[e.getKeyCode()] = false;
   keyPressed = true;
   keyReleased = false;
  }
 }

 public void keyReleased(KeyEvent e) {
  //System.out.println("InputManager: A key has been released code=" + e.getKeyCode());
  if( e.getKeyCode() >= 0 && e.getKeyCode() < 256 ) {
   keys[e.getKeyCode()] = 0;
   key_state_up[e.getKeyCode()] = true;
   key_state_down[e.getKeyCode()] = false;
   keyPressed = false;
   keyReleased = true;
  }
 }

 public void keyTyped(KeyEvent e) { 
  keyCache += e.getKeyChar();
  
 }
Here we use the key code as the index for setting each of the key state.

To use the InputManager you will need to add it as a KeyListener, MouseListener, MouseMotionListener to either your JPanels or JFrames that you are using. Also, at the end of every update frame you will want to call the InputManager.update method to reset the key_up_states.

Here is the full InputManager class I use in my Java 2D engine.
  • InputManager.java - it's back again, let me know if there are issues with this class.
  • InputManagerTest.java - This is a basic test class which shows how to utilize the InputManager to handle basic keyboard events in a polling loop as well as showing how many events you could receive based on your polling rate.

14 comments:

Anonymous said...

InputManager.java link is broken

MorbidMorvick said...

So it is hmmm I thought I had uploaded the InputManager.java when I published the post but I guess not.

Thanks for letting me know!

MorbidMorvick said...

Alright it's there now so give it a try.

Anonymous said...

So do you have to use a timer to implement this?

Nick D said...

Yes, you need to call the update method at the end of every frame, to reset the up states, in order to figure out whether a key has just been released.

Shanti said...

Thanks! I'll use ideas from this. It looks like key_state_down[x]==key_state_up[x] is always false, so you could get rid of one.

Running_Wild said...

Ha! I see you borrowed some of my ideas from our exchange on Gamedev.net ("showInputDialog keeps popping up") so long ago. Glad to have helped! Best wishes.

MorbidMorvick said...

@Running_Wild: Wow that was a long time ago, hehehe. Yeah I eventually broke down and used your idea since the only way to get direct keyboard access (ie look at the keyboard memory segment) in java is to use custom native code. I try to avoid any native code as it falls under the same cross-platform issues. But this is really the only way to achieve the type of input system you want for a game. I only wish java would have included an input device api, not just for keyboard and mouse. Maybe in the future there will be one. In my opinion it would have been beneficial to do so as new input devices become an increasing part of the future.

One final note, the performance of this class in java is adequate on a mediocre machine (single core 2.8ghz winxp), but could be improved in some ways, like handling straight up awt events, cutting out the swing event layers.

Anonymous said...

How do you then implement it in a java frame?

Shanti said...

Anonymous on 3/26/11:
Just make your Frame class
myClass extends Frame implements KeyListener. Makes boolean[] KeysDown an instance variable of myClass. Put the keyPressed, keyReleased, and keyTyped methods in the myClass class.

Anonymous said...

i cant add this as a key listener to my Frame.. and if i do how do i use the isAnykeyUp for example? it doesnt seem to work outside the InputManager.

MorbidMorvick said...

So I added a test class to the post above so that you can see how I've been using this method for polling keyboard input.

https://sites.google.com/site/morbidmorvick/InputManagerTest.java

It's a pretty simple test case to make sure that the InputManager.isAnyKeyDown() and isAnyKeyUp() are working correctly, and from my tests they appear to be working correctly. I also included in there a usage of isKeyUp and isKeyDown and a really simple performance test for how many times the loop detects a key is down.

Hope this helps!

Anonymous said...

This isn't entirely related, but it'd be really cool if you could do a post on why exactly a singleton is bad, and what the alternatives are. As a self tought programmer (hobby) I had never come accross the term but have been using them inadvertently, but cannot see why they are so bad, and what alternatives there are?

Leaderpoll said...
This comment has been removed by a blog administrator.

Post a Comment