WDQuick Links: Tank Software, Tank Ammo ForumsSite Map
William Denniss
tankammo.com
Tank Image Current Projects
PSP Title
I am currently working on a commercial PSP title, details of which will be available on release.
Tank Image Finished Projects
Developed in 2005 with Philip Worthington, "Projected Lineriders cars follow lines that people draw on the surface with pens, speeding up and slowing down according to a visual annotation language. The cars skid and crash and jump over obstacles like hands, bridging the space between the physical and virtual. The game is open ended, nurturing peoples' own creativity and imagination as they strive to create the perfect track."
A high performance accellerated 3D graphics rendering engine and scenegraph with some compatible functionality to the old Java3D scenegraph. I was previously an active member of the Xith3D development team.
Odejava is an API which allows Java developers to use the uses the ODE physics engine with their Java projects and in an Object Orientated fashion. It is capable of working closely with Xith3D. I was previously an active member of the Odejava development team.
Digital picture manager for use with digital cameras and online photo galleries.
Internet password and username remembering program.

3djava Polling the Keyboard State using an Event Driven System20 Oct 2004

Many input systems use so called Input Events. That is, when input is detected, an event is "fired" and the program responds instantly. This is not normally desirable in games where the programmer often wants more control over when user input is acted upon. For renderers such as Xith3D were you can not modify the scenegraph while rendering, this is a larger problem. While one solution is not to use asynchronous input devices like these, they do have their benefits (one being ubiquity) and it is still quite possible to aggregate the events and poll them once per cycle anyway. This tutorial shows one way to poll the keyboard when using an event driven input system. The API used for the examples is Java's AWT.

Update (2004-12-07): I have created a java input abstraction API which provides a solution to this problem and more.

1.1 Possible Solution

To convert the keyboard events into something you can poll is fairly straight forward. One method is to create a Set that will contain the integer keycodes of all the currently pressed keys (remember: elements of a Set are unique so it is impossible to have the same keycode twice, any attempts to do this are silently ignored). How this works is that every time a key pressed event is received, that key's keycode is added to the Set. Similarly, every time a key released event is received, that key's keycode is removed from the Set. Thus, the Set will always contain a list of currently pressed keys, and this list can be polled (i.e. iterated) and actions taken.

Aside: KeyAdapter's keyTyped events are generally unused in games as they are higher level events which don't directly related to whether the key is up or down (they are fired after the key is released). That said, the principles outlined in this tutorial can be applied to pretty much any event should it be needed for whatever reason.

Example:

/*
 * Class Instance Variables
 */
private Set pressedKeys = new HashSet();
    
    //...
    
    /*
     * Initialisation method
     */
    component.addKeyListener(new KeyAdapter () {
            
            public void keyPressed(KeyEvent e) {
                pressedKeys.add(new Integer(e.getKeyCode()));
            }
            public void keyReleased(KeyEvent e) {
                pressedKeys.remove(new Integer(e.getKeyCode()));
            }
            
    });


    /*
     * Key polling method
     */
    for (Iterator i = pressedKeys.iterator(); i.hasNext(); ) {
    
        int keyCode = ((Integer) i.next()).intValue();
                
        switch (keyCode) {
            case KeyEvent.VK_1 :
                // Do action
                break;              
            case KeyEvent.VK_2 :
            
    //...

For keys that are only acted upon once every time they are pressed - simply remove that key from the pressedKeys iterator when it has been processed. If you do this however, be sure to remember that when you are iterating a collection, the only legal way to remove an item from that collection is using the Iterator's remove() method. When the key is released, it will be ``removed'' again from the set but this will just be silently ignored.

1.2 Preventing Access Violations

A problem with the above example code however is that you are not permitted to modify a Collection (the pressedKeys Set) while it is being iterated. To resolve this, a standard mutex can be used to block other threads from accessing it simultaneously.

Example mutex class:

public class Mutex {
    
    private boolean locked = false;
    
    public synchronized void aquireLock() {
        while (locked) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        locked = true;
    }
    public synchronized void releaseLock() {
        locked = false;
        notifyAll();
    }
    
}

To use the mutex - create a single private instance of it and surround all code that uses the pressedKeys Set (i.e. both methods of KeyAdaptor and the poll iteration loop) with a call to the mutex aquireLock() and releaseLock() methods.

1.3 Preventing ``Lost'' Key Presses

The current solution however still has issues. If a user presses and releases a key very quickly (in between polls) it would quite likely go unnoticed. To solve this, we can use a second Set to store released keys. When the key is released, it's keycode is added to the Set of released keys. Every time the keys are polled, the released keys are removed from the pressedKeys Set, and the releasedKeys Set is cleared. Note that if a key is tapped twice in between polls, it will only be registered once due to the nature of Set. Many applications do not want to process the same key twice in a single frame, but if this is desirable then you will need to device a variation which uses a type of Collection without this restriction (e.g. List).

The code from above becomes this (also including the mutex locking):

    
/*
 * Class Instance Variables
 */
private Set pressedKeys = new HashSet();
private Set releasedKeys = new HashSet();
private Mutex keyLock = new Mutex();

    //...

    /*
     * Initialisation method
     */
    component.addKeyListener(new KeyAdapter () {
            
            public void keyPressed(KeyEvent e) {
                keyLock.aquireLock();
                pressedKeys.add(new Integer(e.getKeyCode()));
                keyLock.releaseLock();
            }
            public void keyReleased(KeyEvent e) {
                keyLock.aquireLock();
                releasedKeys.add(new Integer(e.getKeyCode()));
                keyLock.releaseLock();
            }
            
        });
    
    
    //...
    
    /*
     * Key polling method
     */
    keyLock.aquireLock();
    for (Iterator i = pressedKeys.iterator(); i.hasNext(); ) {
    
        int keyCode = ((Integer) i.next()).intValue();
                
        switch (keyCode) {
            case KeyEvent.VK_1 :
                // Do action
                break;              
            case KeyEvent.VK_2 :
                // Do action only once
                i.remove();
            
            //...
        }
    }
    
    pressedKeys.removeAll(releasedKeys);
    keyLock.releaseLock();

In some cases there may be keys which you may wish to ignore if they are released before the keyboard is polled and some keys you don't wish to ignore. The solution to this simple: iterate though the pressedKeys once to process the keys that are not ignored even if they have already been released, then remove the releasedKeys from the pressedKeys Set and iterate though the pressedKeys a second time, taking action on the remaining keys.

The ability to know that a key was pressed even though it may have already been released by the time it is polled is an advantage of this solution and means that the key state can be polled less frequently. The penalty however is that every time a key is pressed, the currently executing thread is interrupted, even if it is only for a millisecond or two while the key is added to the Set.

This sample solution to this common problem is but one way to do it. As the requirements on the input processing system for different games varies, different approaches may be needed.


Return to the Main Page