There are several input API's to chose from when handling input for real-time applications. Different display libraries such as JOGL and LWJGL have completely different default input systems, and LWJGL has two depending on whether you are using it in AWT mode or not. Then there are third party input API's such as JInput and JXInput which can be used with any display library.
Due to this, to prepare ones code well for future changes, it is good practice to implement some sort of abstraction so that one isn't dependant on a particular input API. This is especially important in environments which natively support two separate systems. Xith3D for example allows you to choose between JOGL and LWJGL as your OpenGL layer by changing just a few lines of code. LWJGL has two different modes of operation, native and AWT, each with completely different input systems. To take full advantage of API's such as Xith3D that abstract away the rendering layer, it is important to also abstract input events. By doing this with Xith3D, it is possible to seamlessly switch both the graphics rendering and input from one system to another by changing a few line of code, or even making it a user-selectable option. Allowing the user to chose rendering environments is the topic of another article, but can be a very nice feature to have -- if LWJGL doesn't like your system for some reason, no problem - use JOGL (I have implemented this for my game, and sure enough have found one hardware combination which currently crashes using LWJGL, but not JOGL).
To cater for the need of abstract input processing, I have created an input abstraction API named HIAL (Hybrid Input Abstraction Layer). HIAL abstracts out the system-dependant code, provides generic input interfaces, and has some pre-made input event listeners to save you some time. In short, the system can take AWT or LWJGL events (for example), and spit out generic events that you can convert to the form of your choice (which can be similar to those of AWT or LWJGL if you wish). Other features of HIAL include emulating exclusive-mouse mode (where the cursor is hidden and mouse moves are given in deltas) for input API's which don't directly support it (such as AWT).
More information and code samples can be found at the HIAL Website. Users starting a new project can follow some of the simple examples on that page in building the input system, and have input event abstraction from the start. For users who are currently dependant on the AWT or LWJGL APIs for their input, this article walks you though a simple migration example.
AWT to HIAL
Moving from AWT dependant input processing to abstract HIAL input processing can be done fairly easily. To illustrate, we will convert the Xith3D Getting Started Guide Interaction example over to HIAL. This migration example only covers keyboard inputs. Mouse input events should also be migrated, though as this process is generally simpler to migration keyboard input, once you have migrated the keyboard input, doing the mouse input should be fairly straightforward.
AWT Emulation
HIAL includes some AWT adapter emulation classes which use AWT's own event objects. The difference is that the input is passing though the abstraction layer, which allows us to replace AWT inputs with any other, and also change the output format. The disadvantage is that like the AWT event system, much garbage is created, so it's best to use the emulator to bridge old code and help migrate to pure HIAL listeners.
The original AWT-dependant source code for this example can be downloaded here.
The HIAL AWT emulators emulate the KeyAdapter classes, so if you are using the KeyListener interface instead, switch to using the abstract KeyAdapter class, like so:
Next, change your import statements. We only need KeyEvent from AWT, so it should be specified explicitly (we don't want the rest of AWT as it will class with our HIAL class names).
The class will now actually extend the HIAL emulation of
KeyAdapter instead of the original AWT version, without any further changes. The
keyTyped method is not supported by the emulator due to the fact it listens for the
unicode characters that are generated by the system and not key presses (the latter not being processed
by the abstraction layer). Generally, keyTyped code can be moved into keyReleased
after minor changes (i.e. using e.getKeyCode() instead of e.getKeyChar().
From here, the only real change we need to make is to alter how the event listener is registered with the system.
We remove the cp.getComponent().addkeyListener(this) statement as this is
how you register listeners with AWT's event system. Instead, we instantiate an
AWTKeyboard to capture the events using AWT form the component, and register a
listener (this class). The KeyboardDevice class is actually abstract. For this example,
the AWTKeyboard implementation is used, but we can
replace it with a different implementation such as LWJGLKeyboard in the future (to effectively
change the input system)..
Finally, we add a single method call in the program's main loop - keyboard.update().
This call is used by input synchronous input systems such as LWJGL to fire events. Despite the fact
that the event system we are currently using is asynchronous, that is events are fired from a separate
thread, for compatibility it is wise to have this call to make future changes easier.
That's it! The example now uses HIAL for abstract input. However, the code is still dependant on AWT, and due to the emulation generates a bit of excess garbage. As mentioned in the introduction, AWT emulation is recommended more as a stop-gap than a final solution. The good news is that converting to a more pure HIAL listener is really easy. Read on.
The example source code with the above changes can be downloaded here.
Migrating to a More Pure HIAL Listener
A AWT-like HIAL Keyboard Listener with no dependencies on AWT, and zero garbage generation
exists in net.jtank.input.KeyboardListener.
So instead of extending net.jtank.input.awt.KeyAdapter (HIAL's AWT KeyAdapter emulator),
we implement net.jtank.input.KeyboardListener. This interface also has keyPressed
and keyReleased methods (though no keyTyped), but an int representing the key code
is passed instead of an KeyEvent object as is the case with AWT. This int is equivalent
to what is obtained from the KeyEvent object with the getKeyCode() method.
Replace the KeyCode e parameter with int key. Instead of getting the key code using
e.getKeyCode, simply reference this int directly. Finally,
since we are removing the use of KeyEvent: replace all constants with KeyCode instead (i.e.
KeyEvent.VK_UP becomes KeyCode.VK_UP).
Using find/replace will help here. Thus, our keyPressed method from the example becomes:
That's all - you now have a pure HIAL keyboard listener.
The example source code with the above changes can be downloaded here.
Asynchronous input events (such as those from AWT) are generally not well suited to real time applications
such as games, where the programmer needs greater control over when user input is acted on. With some API's,
modifications can only be made to the scene at certain times (for example in Xith3D's case, when it is not being
rendered). Fortunately with HIAL, it is quite easy to process asynchronous events synchronously, by using one of the
included listener implementations such as net.jtank.input.KeyArrayListener.
Changing from the JOGL display/input system to LWJGL
I claimed before that if you abstract your input with Xith3D, it makes it very easy to switch between JOGL and LWJGL as your rendering and input system. Well here's some proof. The changes are so few, I won't elaborate much.
Change the input and renderer imports:
Change the keyboard device:
The example will now use LWJGL instead of JOGL. Too easy!
The example source code with the above changes can be downloaded here.
It should be noted that the example relies on the fact AWT sends multiple
keyPressed events to continuously move the cube when a key is held down. Generally this is not how you would implement
input, even using AWT as the frequency of these events is unknown and not guaranteed.
The example does this to keep the code simple. In your code you may prefer have an array of
key up/down states which you can set/unset using the invoked methods.
You can use net.jtank.input.KeyArrayListener which has been created to do exactly that.
For more information on its usage, see the main HIAL Page.
LWJGL to HIAL
Moving from LWJGL's event system to HIAL is rather straight forward. Not least because LWJGL's input was designed primarily for real-time applications, and that HIAL was designed with the same goals.
The default listener net.jtank.input.KeyArrayListener has been designed similarly to LWJGL's polled keyboard mode, with very similar functionality.
The method signature is different (isPressed instead of isKeyDown), and more importantly the key constants are different. Unlike
LWJGL, HIAL (and AWT) use ASCII key codes for their keys. This is not a problem if you already use LWJGL's constant fields (which you always should!), it is simply a matter
of replaying instances of Keyboard.KEY_UP with KeyCode.VK_UP for example, and a simple find/replace can do just that. In fact, you can
even use the KeyArrayListener in buffered mode with "sticky" keys ensuring you never miss a key press that you didn't want to, without having to iterate though
events.
The default listener net.jtank.input.KeyEventQueueListener has been designed similarly to LWJGL's event keyboard mode, with similar functionality.
Like with KeyArrayListener, some method signatures are different, the constants are different, but the pattern is the same.
Examples illustrating the similarities and differences between these LWJGL input methods and their HIAL counterparts is available on
the main HIAL web site.
These listeners have more functionality than their LWJGL counterparts (such as sticky keys, demonstrated on the site), and are not your only options.
One can use any of HIAL's listeners, or implement your own by implementing net.jtank.input.KeyboardListener, even to get AWT-like events.
The principles for mouse input are similar to Keyboard. The net.jtank.input.MouseAccumulator
listener can be used both for standard and exclusive-mode mouse operations and contains some useful features such as storing an accumulator (used in exclusive mode).
William Denniss.


Current Projects
Using an Input Abstraction Layer