JaVa
   

Handling Repetitive Key Input

When talking about repetitive key input, we refer to the event of holding down a key and moving an object while the key is held down. Then when the key is released, the object will stop moving. Omitting the synchronization problems highlighted in the last section for the time being, you may be thinking of handling repetitive key input as follows:

public void keyPressed(KeyEvent e)
{
 switch(e.getKeyCode())
 {
 case KeyEvent.VK_LEFT:
 player1.moveLeft();
 break;
 case KeyEvent.VK_RIGHT:
 player1.moveRight();
 break;
 }
}


In order to understand why this is a problem, you need to understand how key events are read into your program. Imagine when using Microsoft Word, the text editor has focus and you hold down the letter s on the keyboard. What happens? Well, an initial s will be displayed followed by a sufficient pause. This pause is designed to allow for time to let go of the key if repetitive input is not wanted. If this pause is passed and the s key is still held down, then s key press events will be dispatched one at a time with an even shorter delay between them. When you hold a key down, an initial key event is passed to the keyPressed method, and then, if the key is still down after a short delay, more key pressed events are passed, separated by shorter intervals. This is not suitable for continuously moving objects at a constant rate; if you use this method to move your objects, their movement will not be fluid, but they will move at the pace of the key pressed events, with pauses in between. You need to record the state of your keys. If the key has been pressed, that is, read by an event received in the keyPressed method, store the fact that the key is currently held down in a variable (Boolean is suitable). When the key is no longer down, that is, read by an event received in the keyReleased method, store in your state variable that the key has been released. In your main loop, you can move your object if the state variable says that the key is currently being held down. Simple. In the following example, AdvancedKeyboard, we handle repetitive key input to move a circle around the screen. Here we also create a KeyProcessor and its associated KeyProcessable interface. Although it is not essential in this example that our key events are synchronized with the main loop, we have added a KeyProcessor class into this example anyway. You may find situations such as these where events do not need to be handled in synch with the main loop, but for some, as we have seen earlier in this chapter, it is essential. The AdvancedKeyboard example contains three classes to go with the KeyProcessor and KeyProcessable interfaces. They are AdvancedKeyboard (main class), Animator, and HotSpot. The HotSpot class used is exactly the same as the one we defined in , so you will need to get the source code for it from there. The Animator class we are using in this example is implemented a little differently from the one we have been using so far. In this Animator class, we handle key events and move the hotSpot object about the applet, depending on the state of the cursor keys Up, Down, Left, and Right. Let's take a look at the code for the remaining four source files (KeyProcessable, KeyProcessor, Animator, and AdvancedKeyboard) for this example (to be compiled with the HotSpot class).

Code Listing 10-5: KeyProcessable
import java.awt.event.*;
public interface KeyProcessable
{
 public void handleKeyEvent(KeyEvent e);
}


Java End example
Code Listing 10-6: KeyProcessor
import java.awt.event.*;
import java.util.*;
public class KeyProcessor
{
 public KeyProcessor(KeyProcessable handler)
 {
 keyEventList = new LinkedList();
 this.handler = handler;
 }
 public void addEvent(KeyEvent event)
 { synchronized(keyEventList)
 {
 keyEventList.add(event);
 }
 }
 public void processKeyEventList()
 {
 KeyEvent event;
 while(keyEventList.size()>0)
 {
 synchronized(keyEventList)
 {
 event = (KeyEvent)keyEventList.removeFirst();
 }
 handler.handleKeyEvent(event);
 }
 }
 private LinkedList keyEventList;
 private KeyProcessable handler;
}


Java End example

The KeyProcessor class is implemented in exactly the same way as the MouseProcessor class in the previous section of this chapter, though this time we only require one event list for key events alone, whereas the MouseProcessor class provided separate lists for mouse events and mouse motion events. However, in this example, we will use it a little differently, by applying the animator object to handle key events, as we shall see.

Code Listing 10-7: Animator
import java.awt.*;
import java.util.*;
import java.awt.event.*;
public class Animator implements KeyProcessable
{
 public Animator(Rectangle bounds)
 {
 this.bounds = bounds;
 keyState = new boolean[256];
 createHotSpot();
 speedX = 4;
 speedY = 4;
 }
 public void createHotSpot()
 {
 Random rand = new Random();
 int diameter = 100+rand.nextInt(200);
 Color col = new Color(rand.nextInt(Integer.MAX_VALUE));
 int xPos = (bounds.width-diameter)/2; // center x
 int yPos = (bounds.height-diameter)/2; // center y
 hotSpot = new HotSpot(new Point(xPos, yPos), diameter, col);
 }
 public void animate()
 {
 if(keyState[KeyEvent.VK_LEFT] &&
 !keyState[KeyEvent.VK_RIGHT])
 moveLeft();
 else if(keyState[KeyEvent.VK_RIGHT] &&
 !keyState[KeyEvent.VK_LEFT])
 moveRight();
 if(keyState[KeyEvent.VK_UP] && !keyState[KeyEvent.VK_DOWN])
 moveUp();
 else if(keyState[KeyEvent.VK_DOWN] &&
 !keyState[KeyEvent.VK_UP])
 moveDown();
 }
 public void moveLeft()
 {
 hotSpot.bounds.x-=speedX;
 if(hotSpot.bounds.x<0)
 hotSpot.bounds.x = 0;
 }
 public void moveRight()
 {
 hotSpot.bounds.x+=speedX;
 if(hotSpot.bounds.x+hotSpot.bounds.width > bounds.width)
 hotSpot.bounds.x = bounds.width-hotSpot.bounds.width;
 }
 public void moveUp()
 {
 hotSpot.bounds.y-=speedY;
 if(hotSpot.bounds.y<0)
 hotSpot.bounds.y = 0;
 }
 public void moveDown()
 {
 hotSpot.bounds.y+=speedY;
 if(hotSpot.bounds.y+hotSpot.bounds.height > bounds.height)
 hotSpot.bounds.y = bounds.height-hotSpot.bounds.height;
 }
 public void render(Graphics g)
 {
 g.translate(bounds.x, bounds.y);
 g.setColor(Color.blue);
 g.fillRect(0, 0, bounds.width, bounds.height);
 hotSpot.render(g);
 g.translate(-bounds.x, -bounds.y);
 }
 public void handleKeyEvent(KeyEvent e)
 {
 switch(e.getID())
 {
 case KeyEvent.KEY_PRESSED:
 keyState[e.getKeyCode()] = true;
 break;
 case KeyEvent.KEY_RELEASED:
 keyState[e.getKeyCode()] = false;
 break;
 }
 }
 public int speedX;
 public int speedY;
 public HotSpot hotSpot; public Rectangle bounds;
 public boolean[] keyState;
}


Java End example

This Animator class is similar to the one created for the AdvancedMouse example in that it is used as a canvas for rendering our display and also contains a HotSpot object. However, this Animator class has an array of type boolean and length 256 called keyState. This array is used to store the current state of all of the keys on the keyboard. The key codes of the keys read in map to values in the range from 0 to 255, which means we are able to set them straight into an array using this value and then test these states using the virtual key codes, such as KeyEvent.VK _LEFT for example. The method handleKeyEvent in this class is invoked via the processKeyEventList method of the keyProcessor object in the main loop thread. When this is invoked, we simply update the state of our keys based on the type of key event received (key pressed or key released). The animate method of this class is called in the main loop thread repeatedly in AdvancedKeyboard (as we shall see in moment) where the HotSpot object is moved accordingly, based on the state set in the keyState array for the respective direction keys. This gives us a fluid repetitive movement (well, at least as fluid as the main loop runs, of course). We will discuss important truths about timing and animation in , "Game Programming Techniques." Here is the code for AdvancedKeyboard.java.

Code Listing 10-8: AdvancedKeyboard.java
import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
import java.awt.event.*;
 public class AdvancedKeyboard extends JApplet implements Runnable, KeyListener
{ public void init()
 {
 getContentPane().setLayout(null);
 setSize(DISPLAY_WIDTH, DISPLAY_HEIGHT);
 setIgnoreRepaint(true);
 animator = new Animator(new Rectangle(0, 0, DISPLAY_WIDTH,
 DISPLAY_HEIGHT));
 backBuffer = new BufferedImage(DISPLAY_WIDTH, DISPLAY_HEIGHT,
 BufferedImage.TYPE_INT_RGB);
 bbGraphics = (Graphics2D)backBuffer.getGraphics();
 addKeyListener(this);
 keyProcessor = new KeyProcessor(animator);
 }
 public void start()
 {
 requestFocus();
 loop = new Thread(this);
 loop.start();
 }
 public void stop()
 {
 loop = null;
 }
 public void run()
 {
 long startTime, waitTime, elapsedTime;
 // 1000/25 Frames Per Second = 40 millisecond delay
 int delayTime = 1000/25;
 Thread thisThread = Thread.currentThread();
 while(loop==thisThread)
 {
 startTime = System.currentTimeMillis();
 // handle mouse events in main loop
 keyProcessor.processKeyEventList();
 // handle logic
 animator.animate();
 // render to back buffer
 render(bbGraphics);
 // render to screen
 Graphics g = getGraphics();
 g.drawImage(backBuffer, 0, 0, null);
 g.dispose();
 // handle frame rate
 elapsedTime = System.currentTimeMillis() - startTime;
 waitTime = Math.max(delayTime - elapsedTime, 5);
 try
 { Thread.sleep(waitTime); }
 catch(InterruptedException e) {}
 }
 }
 public void render(Graphics g)
 {
 animator.render(g);
 }
 public void keyPressed(KeyEvent e)
 {
 keyProcessor.addEvent(e);
 }
 public void keyReleased(KeyEvent e)
 {
 keyProcessor.addEvent(e);
 }
 public void keyTyped(KeyEvent e) {}
 private Animator animator;
 private Thread loop;
 private BufferedImage backBuffer;
 private Graphics2D bbGraphics;
 private KeyProcessor keyProcessor;
 private static final int DISPLAY_WIDTH = 400;
 private static final int DISPLAY_HEIGHT = 400;
}



Java End example

When you run the example, you should get output similar to the following figure, which is a circle object that can be moved smoothly about the viewing area.

Java Click To expand
Screenshot-4:

This time we implement and add the key listener to AdvancedKeyboard itself. This illustrates the complete reusability of the KeyProcessor, as we use this class to receive key event information, but tell the keyProcessor object to handle the key events using the animator object, since the animator object implements KeyProcessable. This can be seen in the constructor of the AdvancedKeyboard class, where we pass a reference to the Animator instance when creating the keyProcessor object. This means that when we make a call to the keyProcessor.processKeyEventList method in the main loop thread, the events are processed in the handleKeyEvents method implemented in the animator object.

JaVa
   
Comments