Más tipos de entrada

Sistemas avanzados para procesar entrada: un InputProcessor, para modularizar la entrada; un InputMultiplexer, para usar más de un procesador a la vez, y un GestureListener, para usar gestos en un móvil.

Cuando trabajamos con LibGDX, manejar la entrada de usuario de forma limpia y eficiente es fundamental para crear juegos que respondan bien tanto en escritorio como en dispositivos móviles. En esta ocasión, vamos a explorar cómo procesar la entrada usando listeners, una técnica que nos permite separar la lógica de entrada del resto del código y hacerla más modular y mantenible.

En lugar de hacer comprobaciones manuales en cada ciclo de renderizado, como hacíamos con polling, utilizaremos el patrón observador. Esto significa que registramos métodos que se ejecutan automáticamente cuando ocurre un evento de entrada, como pulsar una tecla o tocar la pantalla. Así, el motor de LibGDX se encarga de llamar a nuestro código cuando sea necesario, liberándonos de tener que estar preguntando constantemente por el estado de las teclas.

Para ilustrar esto, creamos una clase Coche que extiende de Sprite y mantiene su propio estado, como velocidad y aceleración. Esto nos permite encapsular el comportamiento del coche y actualizar su posición de forma autónoma. Luego, diseñamos una pantalla que contiene este coche y que será la encargada de mostrarlo y actualizarlo.

El siguiente paso es implementar un controlador virtual, que es una estructura de datos que abstrae la entrada real del dispositivo. En lugar de que el juego dependa directamente de qué tecla se pulsa o si se toca la pantalla, el controlador virtual mantiene flags como moverIzquierda o moverDerecha que indican la intención del jugador. Esto facilita cambiar el sistema de entrada sin afectar la lógica del juego, por ejemplo, si queremos pasar de teclado a pantalla táctil o joystick.

Para procesar la entrada, implementamos una clase que extiende InputAdapter, que es una implementación vacía de la interfaz InputProcessor. Esto nos permite sobrescribir solo los métodos que nos interesan, como keyDown y keyUp. En estos métodos, actualizamos el controlador virtual según las teclas pulsadas. Por ejemplo, si se pulsa la tecla izquierda o la tecla A, activamos la bandera moverIzquierda. Cuando se suelta la tecla, la desactivamos. Así evitamos que ambas direcciones estén activas a la vez, lo que podría confundir el comportamiento del coche.

Para manejar la entrada táctil, creamos otro InputAdapter que procesa eventos de ratón o pantalla táctil. Cuando detecta un toque, actualiza el controlador virtual para que el coche se mueva hacia la posición tocada. Además, este controlador táctil puede inhibir la entrada del teclado para evitar conflictos.

Para que LibGDX utilice ambos procesadores de entrada simultáneamente, usamos un InputMultiplexer. Este objeto recibe varios InputProcessor y los ejecuta en orden. Si uno de ellos procesa un evento y devuelve true, el multiplexer no pasa el evento a los siguientes, evitando que se procesen dos veces. Por eso es importante que nuestros métodos devuelvan true cuando manejan un evento y false cuando no.

Con esta configuración, podemos mover el coche tanto con el teclado como con la pantalla táctil sin que se interfieran entre sí. Además, gracias al controlador virtual, la lógica del movimiento está desacoplada de la forma concreta de entrada, lo que facilita futuras ampliaciones o cambios.

Por último, aunque no lo implementamos en este ejemplo, es interesante mencionar que LibGDX también soporta la detección avanzada de gestos en dispositivos móviles. Podemos detectar eventos como longPress, tap, zoom (pinch), pan (arrastrar), y fling (deslizamiento rápido). Para ello, se utiliza la interfaz GestureListener junto con un GestureDetector que convierte estos gestos en eventos que podemos manejar en nuestro código. Esto abre la puerta a controles táctiles más ricos y naturales en juegos para móviles.

En definitiva, usar listeners y multiplexores para manejar la entrada en LibGDX nos permite escribir código más limpio, modular y preparado para múltiples dispositivos, desde teclados y ratones hasta pantallas táctiles con gestos complejos.

A continuación, mostramos un ejemplo simplificado de cómo implementar el controlador virtual y el procesamiento de entrada para teclado y ratón:

public class ControladorVirtual {
    public boolean moverIzquierda = false;
    public boolean moverDerecha = false;
    public boolean obedeceRatón = false;
    public float objetivoX = 0;
}

public class EntradaCocheTeclado extends InputAdapter {
    private ControladorVirtual controlador;

    public EntradaCocheTeclado(ControladorVirtual controlador) {
        this.controlador = controlador;
    }

    @Override
    public boolean keyDown(int keyCode) {
        switch (keyCode) {
            case Input.Keys.LEFT:
            case Input.Keys.A:
                controlador.moverIzquierda = true;
                controlador.moverDerecha = false;
                return true;
            case Input.Keys.RIGHT:
            case Input.Keys.D:
                controlador.moverDerecha = true;
                controlador.moverIzquierda = false;
                return true;
            default:
                return false;
        }
    }

    @Override
    public boolean keyUp(int keyCode) {
        switch (keyCode) {
            case Input.Keys.LEFT:
            case Input.Keys.A:
                controlador.moverIzquierda = false;
                return true;
            case Input.Keys.RIGHT:
            case Input.Keys.D:
                controlador.moverDerecha = false;
                return true;
            default:
                return false;
        }
    }
}

public class EntradaCocheRaton extends InputAdapter {
    private ControladorVirtual controlador;

    public EntradaCocheRaton(ControladorVirtual controlador) {
        this.controlador = controlador;
    }

    @Override
    public boolean touchUp(int screenX, int screenY, int pointer, int button) {
        controlador.obedeceRatón = true;
        controlador.objetivoX = screenX;
        return true;
    }
}

Para conectar ambos procesadores y que funcionen simultáneamente, usamos un InputMultiplexer:

ControladorVirtual controlador = new ControladorVirtual();
EntradaCocheTeclado entradaTeclado = new EntradaCocheTeclado(controlador);
EntradaCocheRaton entradaRaton = new EntradaCocheRaton(controlador);

InputMultiplexer multiplexer = new InputMultiplexer();
multiplexer.addProcessor(entradaRaton);
multiplexer.addProcessor(entradaTeclado);

Gdx.input.setInputProcessor(multiplexer);

Con esta estructura, el juego puede responder a entradas de teclado y ratón de forma ordenada y sin conflictos, manteniendo la lógica de movimiento separada y clara. Además, esta base nos permite extender fácilmente el sistema para incluir gestos táctiles o nuevos dispositivos de entrada en el futuro.

Lista de reproducción
  1. 1
    Instalación y creación de proyectos
    20 minutos
  2. 2
    Festival de la compilación
    21 minutos
  3. 3
    Dibujar por pantalla
    30 minutos
  4. 4
    Procesando entrada
    26 minutos
  5. 5
    Más tipos de imagen
    28 minutos
  6. 6
    Más tipos de entrada
    33 minutos
  7. 7
    Control de resolución
    26 minutos
  8. 8
    Scene2D
    alrededor de 1 hora
  9. 9
    Scene2D UI (parte 1)
    44 minutos
  10. 10
    Scene2D UI (parte 2)
    32 minutos
  11. 11
    Código dependiente de la plataforma
    8 minutos
  12. 12
    Ejecutar código de Android (1/2)
    11 minutos
  13. 13
    Ejecutar código en Android (2/2)
    10 minutos
  14. 14
    Usando el log
    9 minutos
  15. 15
    Internacionalización y localización (1/2)
    9 minutos
  16. 16
    Internacionalización y localización (2/2)
    10 minutos
  17. 17
    Crear juegos con gdx-liftoff y libGDX
    7 minutos