Detecting more complex gestures

Control and user experience should receive a strong focus when you are working on applications in general and games in particular. It is essential that your target audience finds your product to be smooth and flawless. Many elements impact the user experience, such as UI or sound, although a truly crucial one is controls.

Tapping, double tapping, pinching, panning, zooming—there are a myriad of gestures that have almost become an accepted convention. For better or worse, users now expect a certain behavior when they perform an action on a specific game element.

For instance, it is normal for the camera to be moved around when you swing your finger over a map so that the displayed section changes.

Libgdx offers an easy way to detect and react to the most typical gestures. In this recipe, you will learn how to do it through a sample application. We will capture user gestures and show a log on the screen, as we have done in previous input-related recipes.

Getting ready

Before we continue, please double check that you have the sample projects available from your Eclipse workspace.

How to do it…

The code for this recipe is located in the GestureDetectorSample.java file. First, we define a few constants that we will use further ahead, as follows:

private static final int MESSAGE_MAX = 30;
private static final float HALF_TAP_SQUARE_SIZE = 20.0f;
private static final float TAP_COUNT_INTERVAL = 0.4f;
private static final float LONG_PRESS_DURATION = 1.1f;
private static final float MAX_FLING_DELAY = 0.15f;

GestureDetector will do all the necessary heavy lifting to detect gestures for us, letting us enjoy our lives. We also have an array of messages to keep events' information. Naturally, our sample also requires a camera, batch, viewport, and a bitmap font to use when rendering the log messages:

private GestureDetector gestureDetector;
private Array<String> messages;

The create() method has nothing special except for the instantiation of our gesture detector. It takes a few parameters to configure, as follows:

  • float halfTapSquareSize: This denotes half the width (in pixels) of the square of the initial touch event. If the user moves their finger/mouse further than this, it will stop being considered as a long press.
  • float tapCountInterval: This denotes the time (in seconds) for consecutive taps to be considered as part of the same sequence; otherwise, the tap counter will reset.
  • float longPressDuration: This denotes the time (in seconds) that the user has to press for the long press event to fire.
  • float maxFlingDelay: This denotes the time between the detection of a fling gesture and it being reported as an event.

The GestureDetector constructor also takes a GestureListener interface implementation as a parameter. In our case, we pass in a new instance of the GestureHandler inner class, which is defined later. Our newly created gesture detector will be set as the input processor, as it turns out, it conveniently implements such interfaces. This is shown in the following code:

public void create() {
…

  gestureDetector = new GestureDetector(HALF_TAP_SQUARE_SIZE, TAP_COUNT_INTERVAL, LONG_PRESS_DURATION, MAX_FLING_DELAY, new GestureHandler());
  Gdx.input.setInputProcessor(gestureDetector);
}

Let's move on to the render() method because there is nothing to do in dispose(). To render the event information, we iterate the log array, calling the bitmap font's draw() method:

public void render() {
…

  batch.begin();
  for (int i = 0; i < messages.size; ++i) {
    font.draw(batch, messages.get(i), 20.0f, VIRTUAL_HEIGHT - 22.0f * (i + 1));
  }
  batch.end();
}

Our GestureHandler class implements the GestureListener interface and makes every method call addMessage() with a string containing the event information. This is the GestureListener interface definition:

public class GestureHandler implements GestureListener
{
  public boolean touchDown(float x, float y, int pointer, int button);
  public boolean tap(float x, float y, int count, int button);
  public boolean longPress(float x, float y);
  public boolean fling(float velocityX, float velocityY, int button);
  public boolean pan(float x, float y, float deltaX, float deltaY);
  public boolean panStop(float x, float y, int pointer, int button);
  public boolean zoom(float initialDistance, float distance);
  public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2);
}

Just like in the previous examples, the addMessage() method is quite simple. It takes a string and adds it to the messages array. Whenever we exceed the maximum number of messages, we delete the oldest one:

private void addMessage(String message) {
  messages.add(message + " time: " + System.currentTimeMillis());

  if (messages.size > MESSAGE_MAX) {
    messages.removeIndex(0);
  }
}

The result, while not spectacular, makes the point that it is really easy to integrate rich gestures in your Libgdx applications. This approach is certainly legitimate for all platforms. Nevertheless, gestures such as zooming or pinching only make sense with multitouch screens.

How it works…

We have mentioned before that GestureDetector is simply an InputProcessor implementation with some internal logic to identify a set of the most common gestures. Whenever a gesture is detected, the corresponding method in its GestureListener reference is called with the appropriate data. Again, a gesture detector only accepts one listener.

You should be careful when using GestureDetector in combination with InputMultiplexer. A single gesture relies on several events, and it will be a bad idea to have one InputProcessor stop an event from being propagated to the GestureDetector constructor.

Earlier in this recipe, we have listed the GestureListener methods, but here is a more detailed explanation:

  • tap(): This is called every time the user touches and lifts their finger without dragging it outside the tap square. It receives the screen coordinates, the number of taps in the current sequence, and the button used to tap.
  • longPress(): This is called whenever the user touches the screen for a long period of time and takes the screen coordinates.
  • fling(): This is called when the user drags and lifts their finger or the mouse. This receives the velocity along the x and y axis in pixels per second.
  • pan(): This is called when the user drags a finger across the screen. It takes the last known screen coordinates and the deltas for both axes since the last pan event.
  • panStop(): This is called when the user stops panning. It takes the screen coordinates, the finger pointer, and the button.
  • zoom(): This is called when the user performs a zoom gesture with two fingers. It takes the initial and final distances of the zoom.
  • pinch(): This is called when the user pinches; it takes the initial and final positions in screen space of the two involved fingers.

All these methods are quite intuitive. However, in order to fully understand them, the best way is to play around with the recipe sample and see which ones get called as well as seeing when and what kind of data they receive.

There's more…

In this section, we will cover some additional features that the Libgdx gesture detection system can provide you.

Gesture polling

The GestureDetector class also has a few methods that we can use to poll its state. Both versions of isLongPressed() return a Boolean value whether or not the user has had their finger on the screen for longer than a certain amount of time. The first one uses the default long press time, while the second one takes a custom duration. Finally, isPanning() tells us whether the user is currently performing a panning action:

boolean isLongPressed()
boolean isLongPressed(float duration)
boolean isPanning()
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.79.241