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.