Mover con el ratón (parte 2)

Después de diseñar en el vídeo anterior una técnica para mover sprites con el ratón, en este vídeo realizo una implementación sencilla a modo de ejemplo de lo que se planteó.

Este curso ha sido marcado como anticuado y no está siendo revisado de forma activa. Es posible que la información pueda estar desactualizada o que los enlaces se hayan roto.

Vamos a crear un pequeño juego en Java usando Eclipse donde un objeto se mueve de forma fluida por la pantalla siguiendo la posición que indiquemos con el ratón. Para ello, organizaremos el código en un paquete nuevo llamado ratón y crearemos una clase principal que extienda de BasicGame, lo que nos facilitará manejar el ciclo clásico de inicialización, renderizado y actualización.

Primero definiremos las variables que representan la posición actual del objeto en pantalla, PX y PY, que inicialmente colocaremos en (100, 100). Para visualizarlo, dibujaremos un rectángulo pequeño en esa posición. Un detalle interesante es que Eclipse permite usar HotCode Replacing, lo que significa que si modificamos el código mientras el programa está en ejecución en modo depuración, los cambios se reflejan sin necesidad de recompilar ni reiniciar.

Para controlar el movimiento, definiremos variables para la posición de origen (OX, OY) y destino (DX, DY), además de la velocidad en ambos ejes. Para gestionar el estado del movimiento, usaremos una enumeración con dos valores: DETENIDO y MOVIMIENTO. Esto nos ayuda a tener un código más legible y organizado, en lugar de usar valores numéricos arbitrarios.

El movimiento se activará cuando el usuario haga clic con el botón izquierdo del ratón. Para ello, implementaremos el método mouseReleased, que recibe el botón pulsado y las coordenadas del clic. Si el botón no es el izquierdo, simplemente no hacemos nada. En caso contrario, actualizamos las posiciones de origen y destino y calculamos la velocidad necesaria para que el objeto se desplace hacia el punto indicado en un tiempo determinado.

El cálculo de la velocidad se basa en la fórmula que relaciona desplazamiento, velocidad y tiempo. Por ejemplo, la velocidad en X será (DX - OX) / tiempo, y lo mismo para Y. Así, en cada actualización, incrementaremos la posición actual sumando la velocidad multiplicada por el tiempo transcurrido desde la última actualización.

Sin embargo, un problema común al trabajar con números en coma flotante es que la comparación directa para saber si hemos llegado al destino no funciona bien, porque los valores pueden no coincidir exactamente debido a los decimales. Por ejemplo, el objeto puede pasar de 94 a 101 sin pasar exactamente por 100, lo que hace que la condición de parada nunca se cumpla.

Para solucionar esto, en lugar de comparar posiciones, usaremos un contador de tiempo que se reinicia cuando comienza el movimiento y se incrementa en cada actualización con el tiempo transcurrido. Cuando este contador supera el tiempo total que debería durar el movimiento, asumimos que el objeto ha llegado o pasado el destino, y detenemos el movimiento.

Además, para evitar que el objeto quede desplazado del punto exacto al que queríamos moverlo, forzamos que la posición final sea exactamente la de destino cuando el movimiento termina. Esto corrige cualquier pequeño error acumulado durante el desplazamiento.

Como mejora, podríamos ajustar el tiempo de desplazamiento en función de la distancia a recorrer, haciendo que movimientos largos tarden más y así evitar que el objeto se salte el destino por moverse demasiado rápido.

Con este enfoque, conseguimos un movimiento fluido y controlado del objeto en pantalla, que responde a los clics del ratón y se detiene con precisión en el punto indicado.

A continuación, un ejemplo simplificado del código que implementa esta lógica:

import org.newdawn.slick.*;
import org.newdawn.slick.state.BasicGame;

public class MovimientoRaton extends BasicGame {

    private float px, py; // Posición actual
    private float ox, oy; // Origen
    private float dx, dy; // Destino
    private float vx, vy; // Velocidad
    private float tiempoMovimiento = 0.5f; // Tiempo en segundos para el movimiento
    private float contadorTiempo = 0f; // Contador de tiempo transcurrido

    private enum Estado { DETENIDO, MOVIMIENTO }
    private Estado estado = Estado.DETENIDO;

    public MovimientoRaton() {
        super("Movimiento con el ratón");
    }

    @Override
    public void init(GameContainer container) throws SlickException {
        px = 100;
        py = 100;
    }

    @Override
    public void update(GameContainer container, int delta) throws SlickException {
        if (estado == Estado.MOVIMIENTO) {
            // Actualizamos posición
            px += vx * delta;
            py += vy * delta;

            // Incrementamos contador de tiempo
            contadorTiempo += delta;

            // Comprobamos si ha pasado el tiempo de movimiento
            if (contadorTiempo >= tiempoMovimiento * 1000) {
                // Forzamos posición final y detenemos movimiento
                px = dx;
                py = dy;
                estado = Estado.DETENIDO;
            }
        }
    }

    @Override
    public void render(GameContainer container, Graphics g) throws SlickException {
        g.fillRect(px, py, 5, 5);
    }

    @Override
    public void mouseReleased(int button, int x, int y) {
        if (button != 0) return; // Solo botón izquierdo

        ox = px;
        oy = py;
        dx = x;
        dy = y;

        // Calculamos velocidad para que el movimiento dure tiempoMovimiento segundos
        vx = (dx - ox) / (tiempoMovimiento * 1000);
        vy = (dy - oy) / (tiempoMovimiento * 1000);

        contadorTiempo = 0;
        estado = Estado.MOVIMIENTO;
    }

    public static void main(String[] args) throws SlickException {
        AppGameContainer app = new AppGameContainer(new MovimientoRaton());
        app.setDisplayMode(800, 600, false);
        app.start();
    }
}

Con este código, al hacer clic con el botón izquierdo, el pequeño rectángulo se desplazará suavemente hasta la posición indicada y se detendrá con precisión, gracias al control del estado y al manejo del tiempo de movimiento.

Lista de reproducción
  1. 1
    Instalando las herramientas Java
    11 minutos
  2. 2
    Instalando Slick2D
    26 minutos
  3. 3
    Estados e imágenes
    23 minutos
  4. 4
    Respuesta de usuario
    24 minutos
  5. 5
    Colisiones
    25 minutos
  6. 6
    Matemáticas y física
    13 minutos
  7. 7
    SpriteSheet
    13 minutos
  8. 8
    Tilemaps básicos
    23 minutos
  9. 9
    Escalas
    11 minutos
  10. 10
    Animaciones (parte 1)
    14 minutos
  11. 11
    Animaciones (parte 2)
    15 minutos
  12. 12
    Radiografía de un estado
    12 minutos
  13. 13
    Mover con el ratón (parte 1)
    11 minutos
  14. 14
    Mover con el ratón (parte 2)
    13 minutos
  15. 15
    Sonido
    13 minutos
  16. 16
    Música
    14 minutos
  17. 17
    Fuentes
    19 minutos
  18. 18
    Instalar Slick en NetBeans
    6 minutos