Lección 07 (a)
Animación simple e hilos.
Por animación simple entendemos la que se realiza proyectando imágenes en un mismo lugar. En esta sección estudiaremos las clases simpanim y animador que son applets que sirven para realizar animaciones simples con series de imágenes arbitrarias.
Este es el código de simpanim y un ejemplo de aplicación que muestra varias imágenes de satélite en las que se ven las nubes sobre Asia en unas horas y fechas determinadas. El resultado de la animación es que se puede apreciar el movimiento y evolución de las nubes..
Los botones de parar y continuar pertenecen a la página y controlan el applet usando su nombre y sus métodos públicos stop() y start(). Ésta es la forma en que se ha llamado a simpanim para producir la animación.
<applet name="mapas1"
code="ejem07.simpanim.class"
codebase="./"
width="156" height="120"
align="top">
<param name="PASO" value="100">
<param name="PAUSA" value="1000">
<param name="IMA_00" value="ejem07/imagenes/ws051900.jpg">
<param name="IMA_01" value="ejem07/imagenes/ws051903.jpg">
<param name="IMA_02" value="ejem07/imagenes/ws051906.jpg">
<param name="IMA_03" value="ejem07/imagenes/ws051909.jpg">
<param name="IMA_04" value="ejem07/imagenes/ws051912.jpg">
<param name="IMA_05" value="ejem07/imagenes/ws051915.jpg">
</applet>
Y éste es el código html de los botones de parar y continuar.
<FORM>Lo primero que debemos observar es que este applet en su definición dice implements Runnable. Como se estudió en la lección 2, esto significa que el applet está obligado a implementar el único método de la interfaz Runnable que es
public void run();
Éste es el método en el que se va a realizar la animación. Una animación es un proceso que puede requerir durar mucho tiempo y por lo tanto debe implementarse de manera que deje tiempo libre al procesador entre el despliegue de una imagen y la siguiente, para que pueda atender a los otros programas. Esto se logra usando un hilo, o sea un objeto de la clase Thread, al que hemos llamado ani.
Para crear el hilo ani, primero convertimos el applet en un objeto Runnable, con lo que garantizamos que tiene un método public void run(), y luego dentro del método start() usamos la expresión:
ani=new Thread(this);
con lo que indicamos que el hilo ani usará el método run de nuestro applet como su método run. Otra manera de crear un hilo es crear una extensión o subclase de Thread y sobreescribir su método run, pero esto a veces resulta incómodo pues hay que escribir otra clase. El constructor Thread(Runnable r) lo que hace es crear un hilo que tiene al método r.run() como su propio método run. En otras palabras, ani.run() coincide con simpanim.run(). Antes de revisar el contenido del método run analizaremos cómo se leen las imágenes que se van a usar en la animación.
Los nombres de las imágenes que se usan en la animación se pasan al applet mediante los parámetros IMA_00, IMA_01, etc... Dentro del método init() de simpanim puede verse cómo se leen todas estas imágenes. El alumno debe observar que la lectura de imágenes está programada de manera que podrían leerse hasta 100 imágenes (desde IMA_00 hasta IMA_99) y que no es necesario que estén numeradas consecutivamente, el método init lee sólo las que aparezcan en la lista de parámetros del applet. El orden de la animación será el de la numeración y no depende del orden en que aparezcan los parámetros en la llamada al applet. También observamos que para representar la lista de imágenes que se van a proyectar se utiliza un Vector y no una matriz de imágenes. Esto se hace así porque facilita la adopción de las imágenes sin tener que saber de antemano cuántas hay, y también da mayor flexibilidad al usuario del applet.
La animación se realiza en el método public void run(). Allí se crea un ciclo infinito que va proyectando las imágenes de una tras otra. Hay una espera de paso milisegundos entre una imagen y la otra. Para lograr esta espera se utiliza el método ani.sleep(paso) que detiene la ejecución del hilo ani durante paso milisegundos. Cuando se llega al final de la lista de imágenes, se realiza una espera con ani.sleep(pausa) y se vuelve a comenzar la animación. Es muy impotante usar el método sleep del hilo para la espera pues esto es lo que permite liberar los recursos del procesador para otros hilos y otros programas durante la espera.
Ahora presentamos una extensión de simpanim llamada animador.
Este nuevo ejemplo tiene tres novedades. La primera es que es interactivo, es decir, se agregan algunos botones para controlar la animación y poder ver cada imagen por separado. La segunda novedad es que se pueden pasar las imágenes al applet de una manera abreviada usando sólo dos parámetros: BASE y EXT. Esto resulta útil si la aplicación permite cambiar los nombres de las imágenes de manera que tengan una parte fija que es lo que se pasa como parámetro BASE, y el resto del nombre son pares de dígitos entre 00 y 99. La extensión de todas las imágenes debe ser la misma y esto es lo que se indica con el valor del parámetro EXT. Esta forma de leer las imágenes se encuentra en el método getImages(String BASE,String EXT). Ésta es la llamada al applet animador:
<applet
code="ejem07.animador.class" codebase="./"
width="156" height="140"
align="top">
<param name="PASO" value="250">
<param name="PAUSA" value="250">
<param name="BASE" value="ejem07/imagenes/ws0519">
<param name="EXT" value="jpg">
</applet>
La tercera novedad está dentro del mismo método getImages(String BASE,String EXT) y consiste del uso de la clase java.awt.MediaTracker. Se trata de una clase que permite interactuar con el proceso de lectura de las imágenes. En el ejemplo creamos un objeto de esta clase pasándole el applet como parámetro del constructor MediaTracker(Component comp). Luego, usando el método addImage(Image ima,int id), agregamos cada posible imagen al objeto con un identificador numérico diferente para cada una. Esto permite que el "tracker" nos de información sobre la lectura de cada imagen. La llamada al método waitForID(int id) pide al sistema que la imagen con el identificador i sea leida inmediatamente. Finalmente isErrorID(int id) nos informa si hubo algún error en la lectura de la imagen o si ésta se realizó sin contratiempos. Esto es lo que nos permite detectar si una cierta imagen se pudo leer o no y en caso afirmativo la agregamos a las de la animación. El uso del MediaTracker es conveniente para los applets que tienen que leer imágenes de internet y realizar animaciones con ellas, porque el largo tiempo de lectura puede producir que las imágenes aparezcan incompletas y haya parpadeos mientras no se han cargado todas. En realidad debería emplearse este control aún en simpanim; no se hizo solamente para no complicar el primer ejemplo de la lección.
Antes de pasar al estudio de la animación avanzada veremos un ejemplo de animación simple en la que se usan muchos hilos. Comenzamos por construir una sencilla clase llamada giraAguja que es una pizarra (Canvas) que implementa Runnable. La idea es representar a una aguja (como de un reloj) girando. Éste es el código:
En esta clase se ilustra un método muy útil para realizar animaciones sin parpadeos. Consiste en el uso de una imagen artificial sobre la que se dibuja para crear lo que se quiere poner en pantalla antes de exhibirlo. Al final se proyecta esa imagen a la pantalla (en este caso a la pizarra). El alumno deberá comprobar que si en el siguiente ejemplo elimina el uso de la imagen auxiliar auxima y realiza los dibujos directamente sobre la pizarra, se produce un parpadeo muy desagradable a la vista.
Ahora creamos un applet que utiliza 16 de estas agujas giratorias:
Haciendo un click sobre uno de los círculos se puede cambiar la dirección de giro de la aguja.
Este ejemplo muestra que pueden convivir muchos hilos simultáneamente, siempre y cuando cada uno de ellos pase la mayor parte de su vida "durmiendo". El ciclo del método run de una animación debe hacer siempre una llamada al método sleep del hilo, se recomienda un mínimo de 20 milisegundos, pero si puede ser mayor, mejor. El tiempo que debe transcurrir entre la presentación de una imagen y la siguiente para que haya una apariencia de movimiento suave es de entre 40 y 60 mms (milisegundos). Si el tiempo dT el tiempo que el procesador ocupa en realizar el trabajo de un ciclo es mucho menor que 40 mms entonces conviene poner un sleep de 40 milisegundos y así podrán funcionar varias animaciones simultáneas, pero si dT es mayor que 60 mms no se podrá presentar ni una sola animación de manera satisfactoria. Por supuesto dT depende tanto de la complejidad del ciclo como de la velocidad del procesador que se utiliza. Si el programador no incluye un sleep dentro del ciclo de una animación, puede bloquear la computadora de su cliente, incluso con una sola animación.
José Luis Abreu y Marta Oliveró