smooth method to react to keyboard input?

hi,

what is the smoothest/fastest way of reacting to user keyboard input (in order to move the player) ?

Right now I’m doing it like this:

  • user presses a key: the key which is pressed is remembered (for example: Globals.isMovingForward = true )
  • a Thread which is always running in the backround checks the pressed keys and reacts by moving the player modell

But that approach is not smooth. It feels slow and its jerking (I tried different Thread priorities).

How do you do it?

Thanx, Usul

Some relevant examples of the way I do it:

class EventMouseKeyboard
implements KeyListener
{
    public void keyPressed(KeyEvent e)
    {

        switch( e.getKeyCode() )
        {
            case KeyEvent.VK_W :
                Globals.setMoveForward(true);
            break;
            ...
        }
    }
    
    public void keyReleased(KeyEvent e)
    {
        switch( e.getKeyCode() )
        {
            case KeyEvent.VK_W :
                Globals.setMoveForward(false);
            break;
         }
    }
}
public class Globals
{
    private static boolean isMoveForward = false;
 
   public static synchronized boolean getMoveForward()
    {
        return isMoveForward;
    }

    public static synchronized void setMoveForward(boolean isPressed)
    {
        isMoveForward = isPressed;
    }
}
public class TheThread
extends Thread
{
    private TransformGroup transformGroup;
    private Transform3D transform3D;

    private double moveRate = 0.2;
    private double rotateAmount = Math.PI / 25.0;

    TheThread(TransformGroup transformGroup)
    {
        this.transformGroup = transformGroup;
        transform3D = new Transform3D();
        setPriority(Thread.NORM_PRIORITY + 2);
    }

    public void run()
    {

        while(true)
        {
            try
            {
                Thread.sleep(30);
            } catch (InterruptedException ex)  {}

            if( Globals.getMoveForward() )
            {
                moveForward();
            }
        }
    }
}

I would suggest not to seperate the input gathering and input reacting threads.

In my opinion you should handle input gathering and then input reacting immediately afterwards, in the same thread. That way input is processed as soon as it arrives, in the same frame that it arrives.

That doesnt work. Try the following: type “AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA”. If you hold down the “A” key, you’ll notice that the first “A” is written immediately. Then there is a short pause before the other “A”'s are written.
And thats the problem with handling the key input immediately. It doesnt behave natural then.

But I found the workaround:
I’m saving the system time when the key was pressed. Then I’m computing the way which was left behind by multiplying the speed with the time since the key was pressed. That way I’m independent of when the Thread is called. And it works perfectly, the figur now moves smoothly as it should.

But is there any other way? I dont think that my method is the perfect solution. So again: How do you do it?

You must not use Threads to do this in Java3D. To change the graph you must use behaviors.
You can set the triggers for it to be time elapsed, frames elapsed, AWT Events (Key, mouse, others), ad others.
Here is a behavior example :


public class MoveBehavior extends Behavior{
  TransformGroup target;
  Transform3D t3d;
  WakeupOnElapsedTime trigger; //This time it will be triggered by time
  public MoveBehavior(TransformGroup target){
    this.target = target;
    t3d = new Transform3D();
    trigger = new WakeupOnElapsedTime(1000 / 60);
  }
  public void initialize() {
    this.wakeupOn(trigger);
  }
  public void processStimulus(Enumeration criteria) {
    //the enumeration has all the triggers that meet its condition,
    //In this case is only one, so no iteration to define the response
    //Do the transforms
    t3d.rotX();
    //Set the transform
    target.setTransform(t3d);
     //Set the trigger for the nest time
    this.wakeupOn(trigger);
  }
}

To put the behavior in the scenegraph you use something like this:


  //In the graph creation
  branchGroup.addChild(groupToChange);
  MoveBahavior mv = new MoveBehavior(groupToChange); //The group you want to move
  mv.setSchedulingBounds(new BoundingSphere(new Point3d(),100.0f));
  branchgroup.addChild(mv);

To receive the keyboard events you may use:


    trigger = new WakeupOnAWTEvent(AWTEvent.KEY_EVENT_MASK);

Read the Javadocs!
If you have troubles just ask!

Hope it helps,
Rafael.-

Thank you rdcarvallo.

I have no trouble using behaviors. But you said “you must not use threads”.

Whats so wrong with threads? What disadvantages/risks do they have?

Java3D uses its own threads to run behaviors and rendering. It’s not a good idea to change things in middle of their work.
Example:
Your player object is a Tank(with two shapes Body and Turret)
The J3D Render Thread begins to draw your Tank, trasnforms the body and draw the polygons
You change the Transform to move forward
The J3D Render Thread continues to draw your Tank, trasnforms the turret and draw the polygons
Now you have a mutant Tank with the turret moved forward!

Other cases could be to change the camera rotation in the middle of the rendering of your scene , etc.

Using behaviors you wil update the transform always in a synchronized way with the rendering.

Rafael.-

Thats a good point Rafael!

OK, so now I use Behaviors.

I use a KeyBehavior which reacts on the user pressing or releasing a key. When he does this, the state of the key is safed along with the time of the action. But when the Behavioris called because a key is pressed which is already pressed then the time is not safed (because it was already safed).

Then I have a second Behaviorwhich uses WakeupOnElapsedTime in order to act like a thread. It checks if a key is pressed and then moves the player accordingly.

So, for example, when the user presses the key “w” in order to move forward these things happen:

  • the KeyBehavior class is called and it sets the boolean isMoveForward to true and the long timeMoveForward to the current time
  • the KeyBehaviour class is called again, because the user did not yet release the “w” key: nothing happens
  • the ThreadBehavior awakes and checkes if isMoveForward is true
  • the ThreadBehavior gets timeMoveForward and moves the player.
  • the ThreadBehavior sets timeMoveForward to the current time
  • all this is repeated until the user releases the “w” key
  • the user releases the “w” key: KeyBehavior sets isMoveForward to false

It works as smoothly as my previous approach (It feels perfect).

Does that sound like the right way to do it now?

It seems OK,

 I have an [b]Input[/b] object shared for the two behaviors, then the [b]InputBehavior[/b] sets the input flags (up,down,left,right,fire, etc) and the [b]ShipMovementBehavior[/b] reads them and acts as needed.

 I dont't understand very well your time keeping, but if that gives you fine  smooth results, use it ;)

 Rafael.-

Thanx a lot Rafael!

So one problem is solved, lets find out what the future brings.