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 Using an Input Abstraction Layer26 Aug 2005

A Java Input Processing Best Practice

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:

-public class Interaction implements KeyListener +public class Interaction extends KeyAdapter

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).

-import java.awt.event.*; +import java.awt.event.KeyEvent; +import net.jtank.input.*; +import net.jtank.input.awt.*;

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.

- cp.getComponent().addKeyListener(this); + KeyboardDevice keyboard = new AWTKeyboard(cp.getComponent()); + keyboard.registerListener(this);

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.

// main rendering loop while(true) { view.renderOnce(); + keyboard.update(); // etc...

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:

public void keyPressed(int key) { switch(key) { case KeyCode.VK_UP : rotX+=0.05; isRotationScheduled=true; break; case KeyCode.VK_DOWN : rotX-=0.05; isRotationScheduled=true; break; case KeyCode.VK_LEFT : rotY+=0.05; isRotationScheduled=true; break; case KeyCode.VK_RIGHT: rotY-=0.05; isRotationScheduled=true; break; case KeyCode.VK_ESCAPE: System.exit(0); break; } }

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:

-import net.jtank.input.awt.*; +import net.jtank.input.lwjgl.*; -import com.xith3d.render.jogl.*; +import com.xith3d.render.lwjgl.*;

Change the keyboard device:

- KeyboardDevice keyboard = new AWTKeyboard(cp.getComponent()); + KeyboardDevice keyboard = new LWJGLKeyboard(LWJGLKeyboard.BUFFERED);

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.




Return to the Main Page