¿Cómo animar un objeto en 2D?

Movimiento básico

La sensación de movimiento que produce la animación en dos dimensiones es una ilusión óptica conseguida mediante la proyección sucesiva de imágenes estáticas con ligeras diferencias entre cada una y la siguiente. A cada imagen se le llama frame y para conseguir un efecto creible se suelen proyectar unas 24 o 25 imágenes por segundo.

Para simular movimiento en un programa actuaremos de forma similar. Escribiremos un bucle que se encargará de crear un frame nuevo completo en cada iteración. Si de un frame a otro se dibujan los elementos en posiciones ligeramente distintas y somos capaces de realizar 24 o 25 iteraciones por segundo obtendremos el efecto deseado.

Por ejemplo, si queremos mover un círculo por la pantalla en cada frame lo dibujaremos en una posición distinta. Es decir, las coordenadas del centro del círculo cambiarán de un frame al siguiente. Por lo tanto tendremos que usar dos variables, declaradas fuera del bucle, para representar las coordenadas del círculo, y en cada iteración les daremos un nuevo valor.

Si x e y son las dos variables y queremos que el círculo sólo se mueva horizontalmente bastará con sumar una cantidad fija a la coordenada x antes de dibujar cada frame. Esta cantidad fija podría ser una constante llamada VX.

double x = 160; double y = 160; double radio = 20; void dibuja() { x = x + 2; background(0,0,0); ellipse(x,y,radio*2,radio*2); text("x = "+x, 10,20); } void main() { animate(dibuja); }

 


// Para cada nuevo frame se calcula la nueva posición de x
x = x + VX; // VX es una constante de tipo entero que vale 2
// El círculo se dibuja en sus coordenadas (x,y)
		

Si queremos que la pelota se mueva también en vertical podemos hacer lo mismo con la variable y. De este modo tendremos dos constantes, una para actualizar el valor de x a la que podemos llamar VX y otra para el de y a la que podemos llamar VY.

double x = 80; double y = 80; double radio = 20; void dibuja() { x = x + 2; y = y + 1; background(0,0,0); ellipse(x,y,radio*2,radio*2); text("x = " + x + " y = " + y + " VX = 2 VY = 1", 10,20); double m = 25*sqrt( 2*2 + 1*1 ); text("Velocidad = 55.9 pixels/s", 10,40); } void main() { animate(dibuja); }

 


// Para cada nuevo frame se calcula la nueva posición de x e y sumando VX y VY respectivamente
x = x + VX; // VX es una constante de tipo entero que vale 2
y = y + VY; // VY es una constante de tipo entero que vale 1
// El módulo de la velocidad será
m = fps * sqrt( VX*VX + VY*VY )
// El círculo se dibuja en sus coordenadas (x,y)
		

Cuanto mayor sea la cantidad que sumamos a x antes de dibujar cada frame más rápido será el movimiento. Es decir, hay una relación entre esta cantidad y la velocidad a la que se mueve. Concretamente, si estamos dibujando 25 imágenes por segundo, y si cada vez se suma una cantidad V fija, por ejemplo 5, la pelota se estará moviendo a 25*5 = 125 pixels/s.

Del mismo modo, si sabemos las componentes de la velocidad y el número de frames por segundo (fps) podemos calcular el módulo de la velocidad multiplicando los fps la raiz cuadrada de la suma de los cuadrados de las componentes de la velocidad.

Movimiento hacia un objetivo concreto

Siguiendo con el ejemplo anterior, si en lugar de usar dos constantes usamos dos variables (vx y vy) para representar las componentes horizontal y vertical de la velocidad, podemos hacer que el círculo se mueva en diferentes direcciones durante la ejecución del programa al variar el valor de dichas variables.

Por ejemplo, si lo que queremos es que el movimiento acerce el círculo hacia un punto concreto tendremos que determinar los valores adecuados para vx y vy. En concreto, si suponemos que el destino está en las coordenadas (dx,dy) y el círculo en las coordenadas (x,y) podemos restar ambas coordenadas para obtener así un vector cuya dirección será la correcta pero cuyo módulo será tan grande como la distancia entre ambos puntos. Si normalizamos dicho vector dividiéndo sus componentes por su módulo obtendremos un vector unitario cuyas coordenadas servirán para calcular las componentes de la velocidad del círculo (vx,vy).

double x = 160; double y = 160; double radio = 20; double vx = 0; double vy = 0; void dibuja() { vx = mouseX - x; vy = mouseY - y; double m = sqrt( vx*vx + vy*vy ); if ( m > 0 ) { vx /= m; vy /= m; } x = x + vx; y = y + vy; background(0,0,0); ellipse(x,y,radio*2,radio*2); text("El círculo sigue al ratón", 10,20); } void main() { animate(dibuja); }

 


// Suponiendo que mouseX y mouseY tienen las coordenadas del ratón, para cada nuevo frame 
// se calcula la nueva velocidad en función de la posición del ratón y la del círculo
vx = mouseX - x;
vy = mouseY - y;
// Calculamos módulo de la velocidad para normalizarlo haciéndolo unitario
double m = sqrt( vx*vx + vy*vy );
if ( m > 0 ) {
  vx = vx / m;
  vy = vy / m;
}
// Se calcula la nueva posición en función de la velocidad actual
x = x + vx; 
y = y + vy;
// El círculo se dibuja en sus coordenadas (x,y)
		

Movimiento en función del ángulo

Si lo que queremos es mover un objeto a cierta velocidad y en cierta dirección especificada mediante el ángulo con la horizontal tendremos que recordar algo de trigonometría y usar las funciones seno y coseno. Además, normalmente estas funciones trabajan con radianes por lo que habrá que convertir de grados a radianes sabiendo que 360 grados equivalen a 2*PI radianes.

double x = 160; double y = 160; double radio = 20; final double V = 1; double alpha = 0; void dibuja() { double vx = V * cos(alpha * PI / 180.0); double vy = V * sin(alpha * PI / 180.0); x = x + vx; y = y + vy; background(0,0,0); stroke(0); ellipse(x,y,radio*2,radio*2); stroke(255,0,0); line( 160,160, 160 + vx*radio*2, 160 + vy*radio*2 ); text("Flecha izquierda: Resta 5 grados", 10, 20); text("Flecha derecha: Suma 5 grados", 10, 40); text("Grados: " + alpha, 10, 60); if ( keyPressed && key == "left" ) alpha -= 5; if ( keyPressed && key == "right" ) alpha += 5; if ( x > 320 ) { x = 0; } if ( y > 320 ) { y = 0; } if ( x < 0 ) { x = 320; } if ( y < 0 ) { y = 320; } } void main() { animate(dibuja); }

 


// Para cada nuevo frame se calculan las componentes de la velocidad en función del ángulo alpha
//  y de la velocidad Va la que se desea ir
vx = V * cos( alpha * PI / 180.0 );
vy = V * sin( alpha * PI / 180.0 );
// Se calcula la nueva posición en función de la velocidad actual
x = x + vx; 
y = y + vy;
// El círculo se dibuja en sus coordenadas (x,y)
		

Movimiento acelerado

La aceleración se produce cuando la velocidad cambia a lo largo del tiempo. Podemos simular la acelaración modificando el valor de las componentes de la velocidad. Si siempre sumamos la misma cantidad tendremos una aceleración constante. Si el valor de la aceleración depende de algún evento, por ejemplo, la pulsación de una tecla, la aceleración será variable.

double x = 20; double y = 160; double vx = 0; double radio = 20; void dibuja() { x = x + vx; vx = vx + 1; background(0,0,0); ellipse(x,y,radio*2,radio*2); text("x = " + x + " vx = " + vx, 10,20); } void main() { animate(dibuja); }

Movimiento aleatorio

Generando valores aleatorios para las variables que controlan la velocidad de un objeto conseguiremos que este se mueva en una dirección aleatoria con una velocidad que también será variable.

Si queremos controlar el módulo de la velocidad pero generar una dirección aleatoria es más sencillo generar un número aleatorio que actúe como ángulo y usar trigonometría (cosenos y senos) para calcular los valores de vx y vy antes de multiplicarlos por el valor deseado para el módulo de la velocidad.

En algunas situaciones es necesario simular un comportamiento aleatorio pero limitado a movimientos horizontales o verticales. En este caso, lo más sencillo es contar iteraciones y cada vez que se alcance cierto valor (constante, o también generado aleatoriamente) cambiar de dirección eligiendo primero, también al azar, cuál de las cuatro:izquierda, derecha, arriba o abajo. Y luego generando los valores adecuados para vx y vy.

double x = 160; double y = 160; double vx = 0; double vy = 0; double radio = 20; int contador = (int)random(25); void dibuja() { background(0,0,0); if (contador <= 0) { contador = (int)random(25); if ( ((int)random(2)) % 2 == 0 ) { vx = 3+random(10); if ( ((int)random(2)) % 2 == 0 ) vx = vx * -1; vy = 0; } else { vx = 0; vy = 3+random(10); if ( ((int)random(2)) % 2 == 0 ) vy = vy * -1; } } else { contador = contador - 1; } x = x + vx; y = y + vy; if ( x < -radio ) x = 320+radio; if ( x > 320+radio ) x = -radio; if ( y < -radio ) y = 320+radio; if ( y > 320+radio ) y = -radio; ellipse(x,y,radio*2,radio*2); text(" vx = " + ((int)vx) + " vy = " + ((int)vy)+ " contador = " + ((int)contador), 10,20); } void main() { animate(dibuja); }