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.