Lección 07 (b)


Animación avanzada (billar).


Entendemos por animación avanzada la que se realiza moviendo imágenes sobre un componente (normalmente una pizarra o un Panel). Las imágenes que se mueven pueden ir cambiando durante el movimiento. Al igual que la animación simple, la avanzada requiere del uso de imágenes auxiliares sobre las que se prepara lo que se proyectará en la pantalla, pero en este caso puede convenir limitar la proyección a la mínima zona necesaria para cada figura móvil pues de otra manera se puede caer en un proceso muy lento.

Todo el material de esta sección se ilustrará con dos ejemplos: billar y mueveTextos. Se trata de dos applets que ya son algo  sofisticados. El primero es un juego de billar. El applet representa una mesa de billar con tres pelotas. Bajando el botón del mouse sobre una pelota y arrastrando el cursor de puede "apuntar" ayudándose de dos segmentos de recta que aparecen uno de ellos entre el centro de la bola y la punta del cursor y el otro apuntando en dirección contraria y del triple de tamaño que el primero. La bola saldrá disparada al soltar el botón del mouse con una velocidad proporcional al tamaño de los segmentos y en la dirección del segundo. La bola se mueve sobre la mesa realizando choques elásticos en las orillas y contra las otras bolas. Las bolas en movimiento van perdiendo velocidad debido a un amortiguamiento, hasta que se paran. El alumno debe jugar un rato con este applet y si tiene alguna habilidad para el billar podrá comprobar que la simulación es bastante realista, excepto por los efectos de rotación que, para simplificar el ejemplo, no se toman en cuenta.

Después de jugar con el applet el alumno debe estudiar cuidadosamente su construcción. El applet utiliza tres clases: R2, bola y billar. Explicaremos brevemente las tres clases poniendo mayor énfasis en el método

public static void dibujar(bola[] B,Image fondo,Image imagen,Component cmp)

de la clase bola que es el que contiene la esencia del método de animación avanzada denominado de "doble imagen" (en inglés: "double buffer").

La clase R2 representa vectores en el plano. Tiene todas las operaciones que se pueden realizar con vectores en el plano. Cada objeto de la clase R2 es un vector con abscisa x y ordenada y.

Si su navegador reconociese la etiqueta de applet, aquí vería un applet

La clase R2 no tiene nada que ver con la animación. Se trata de una clase auxiliar que se utiliza para representar las posiciones y velocidades de todas las bolas de la mesa de billar y para controlar su movimiento y sus choques. Se trata de una clase muy útil que el alumno podrá usar en otras aplicaciones, por lo que conviene que la estudie detenidamente y comprenda cada uno de sus métodos.

La clase bola es la más compleja pues representa no sólo cada bola sino también el movimiento de un conjunto de bolas. Por un lado tiene las variables que definen una bola (R=radio, p=posición,v=velocidad, pa=posición anterior,t=vector unitario en la dirección de la velocidad y n=vector unitario perpendicular a t) y los métodos para controlarlas. Luego tiene los métodos para representar el avance, el amortiguamiento, los rebotes contra las paredes y los choques contra las otras bolas. Por otro lado tiene métodos estáticos para distribuir las bolas al azar y para controlar el avance, los rebotes y los choques de un conjunto de bolas (estos mismos métodos podrían controlar una mesa con más de tres bolas). Finalmente está el método

public static void dibujar(bola[] B,Image fondo,Image imagen,Component cmp)

que se encarga de dibujar las bolas en el componente cmp. Este método contiene la esencia de lo que el alumno debe aprender sobre animación avanzada, el llamado método de "doble imagen". El alumno debe revisar primero los otros métodos de la clase bola y comprender su función.

Si su navegador reconociese la etiqueta de applet, aquí vería un applet

La implementación de los choques entre bolas es algo complicada. Se trata de un choque perfectamente elástico y la implementación utiliza la descomposición de las velocidades de las bolas en sus componentes normal y tangencial al plano de contacto. En un choque elástico de bolas idénticas, las dos bolas conservan sus componentes tangenciales pero intercambian sus componentes normales. Esto es precisamente lo que se hace en la implementación. Por ejemplo cuando una bola está en reposo y el choque es frontal, las componentes tangenciales son cero y las normales se intercambian de manera que la bola que estaba en movimieno queda en reposo y la otra adquiere la velocidad de la que se movía. Otro detalle que se cuida es que cuando se detecta que hay un choque, entonces se regresa la bola al punto exacto donde el choque se produce, para hacer los cálculos y, después del choque, se avanzan las dos bolas el equivalente al tiempo transcurrido desde el choque.

Ahora estudiaremos el método que dibuja las bolas

public static void dibujar(bola[] B,Image fondo,Image imagen,Component cmp)

A este método se le pasan como parámetro las bolas, dos imágenes llamadas fondo e imagen, y un componente que en el caso de esta aplicación es el applet mismo.   El fondo, como se puede ver en el código del applet billar, que se presenta más adelante, es la imagen de la mesa de billar (sin bolas). A pesar de que en realidad las mesas de billar son de un solo color, para ilustrar que este método sirve para cualquier imagen de fondo, hemos usado un fondo cuadriculado. Las bolas se mueven por encima del fondo. Por otro lado, imagen es una copia de lo que se ve en el componente cmp, es decir, la mesa y las bolas.

El método de la doble imagen consiste en mantener siempre una copia de la imagen de fondo (fondo) y una copia de la imagen que se ve (imagen) y cuando se hace un cambio de posición de los objetos móviles, primero se "limpia" imagen copiando las partes del fondo necesarias para que la imagen sea igual al fondo, luego se dibujan en imagen los nuevos objetos y finalmente se copian al componente visible cmp (o sea a la pantalla) todas las zonas modificadas en imagen, ya sea por haber copiado en ellas el fondo o por haber dibujado en ellas los objetos móviles en su nueva posición.

En el caso concreto de nuestro ejemplo, primero se "limpian" las zonas que tenían bolas (copiándolas de fondo a imagen), luego se dibujan en imagen las bolas en su nueva posición y finalmente se copian al applet las zonas modifcadas. Estos pasos pueden apreciarse claramente en la implementación del método dibujar.

Para copiar solamente las regiones afectadas de fondo a imagen y luego de imagen a cmp, se utiliza el método g.clipRect(int x,int y,int w,int h) cuya función es informar a g (objeto de la clase Graphics) que las próximas operaciones de dibujo deben limitarse al rectángulo deteminado por (x,y,w,h) . Por tanto las operaciones g.drawImage sólo actúan en el rectángulo indicado. Cuando los objetos que se mueven son varios y pequeños, éste es el método que se debe emplear. Si los objetos son grandes o si la imagen completa es pequeña (por ejemplo menos de 40000 pixels), se puede simplificar ligeramente el método de la doble imagen.

El método simplificado de la doble imagen consiste en lo siguiente:   primero se copia el fondo completo a imagen (sin usar clipRect), luego se dibujan los nuevos objetos sobre imagen y finalmente se copia (en un solo paso) imagen a cmp. El método simplificado se usa en el otro ejemplo de esta sección (mueveTextos). Pero el método simplificado es lento cuando el fondo es grande, como en billar. Si también los objetos que se mueven en la pantalla son grandes, entonces ninguno de los dos métodos funcionará muy bien, y entonces suele convenir usar el simplificado. Pero cuando la imagen es grande y los objetos en movimiento son pequeños, el método completo que usamos en este ejemplo es el indicado.

El alumno observará que se usan repetidamente los métodos getGraphics de imagen y de cmp. Esto se hace por dos razones, una es que a veces se dibuja sobre imagen y a veces sobre cmp, pero también se hace porque cada vez que se llama al método g.clipRect, el objeto g (de la clase Graphics) queda limitado y la única manera de eliminar esta limitación o "clipping" es renovarlo volviendo a llamar al método getGraphics.

Una vez explicado el método de la doble image, veamos el applet billar.

Si su navegador reconociese la etiqueta de applet, aquí vería un applet

El alumno debe observar que billar, como todos los applets que hacen animaciones, implementa Runnable. De hecho es precisamente en el método run() donde se hacen las llamadas a los métodos de la clase bola que controlan los movimientos y los choques. En el método run() de billar puede verse cómo se realizan las operaciones de avanzar, amortiguar, controlar rebotes y dibujar las bolas. Después de cada ciclo, se hace una llamada al método sleep del hilo del applet para liberar, aunque sea unas milésimas de segundo, el procesador mientras se espera el tiempo adecuado para realizar otro paso de la animación (o más bien simulación). Se usa un tiempo muy corto en sleep para que la simulación sea realista, pero esta animación es costosa en tiempo de procesador y no puede convivir bien con otros applets de animación.

En su método init() el applet crea las tres bolas y carga unos AudioClips pluk.au y clok.au que se usan como efectos de sonido en los choques.

En el método start() se crean las dos imágenes (fondo que es el dibujo de la mesa de billar y la copia imagen) que se usan para el método de la "doble imagen" explicado en párrafos anteriores. Esto no debe hacerse en el método init() pues cuando init() se ejecuta el tamaño de los componetes aún no está definido, hay que hacerlo en start() o en run().

El método paint(Graphics g) aprovecha imagen para actualizar la pantalla. Ésta es la manera más recomendable de tratar el método paint cuando el applet realiza animaciones: proyectando la imagen que es copia de lo que se ve y que siempre hay que tener a mano para el método de la doble imagen. El método update(Graphics g) se sobreescribe para que sólo llame a paint(g). Esto es lo recomendable cuando paint sólo proyecta la imagen. Si no se sobreescribe update entonces en las actualizaciones primero se pinta el rectángulo con backcolor y después se pone la imagen. Sobreescribiendo update se puede evitar el paso de pintar el rectángulo y el resultado es más agradable a la vista pues elimina el parpadeo.

Finalmente, los métodos mousePressed, mouseDragged y mouseReleased controlan el dibujo de la línea que determina la dirección y la velocidad que se imprimirá a la bola. Los dibujos de las líneas se hacen directamente sobre la pantalla con el modo XOR para poder borrar volviendo a pintar lo mismo. En mousePressed se investiga antes si el botón pulsado es el derecho, en cuyo caso se devuelven las bolas a su posición anterior usando una clonación B0 de la matriz B de las bolas, que se actualiza cada vez que se dispara una bola en el método mouseReleased. El alumno debe estudiar detenidamente el control que se realiza en estos tres métodos de los segmentos pues representa otro tipo de animación que sólo ilustraremos con este ejemplo pero que deberá aplicar en uno de los ejercicios.


Índice

Lección 07 (c)

Ejercicios de la Lección 07.


José Luis Abreu y Marta Oliveró