Animación en applets de Java

Este artículo describe cómo implementar la animación utilizando la API de subprograma de Java. Describe técnicas de uso común y ofrece un ejemplo sencillo para ilustrar cada técnica.

Técnicas básicas de animación

Muchas formas de animación son posibles en Java. Lo que todos ellos tienen en común es que crean algún tipo de movimiento en la pantalla dibujando fotogramas sucesivos a una velocidad relativamente alta (normalmente entre 10 y 20 veces por segundo).

Empezaremos por crear un applet de plantilla simple para hacer animaciones y lo elaboraremos lentamente hasta llegar a un applet bastante completo.

Usando un hilo

Para actualizar la pantalla varias veces por segundo, debe crear un nuevo hilo de Java que contenga un bucle de animación. El bucle de animación es responsable de realizar un seguimiento del fotograma actual y de solicitar actualizaciones periódicas de la pantalla. Para implementar un hilo, debe crear una subclase de Threado adherirse a la Runnableinterfaz.

Un error común es poner el bucle de animación en el paint()método de un subprograma. Hacerlo tendrá efectos secundarios extraños porque retiene el hilo principal de AWT, que está a cargo de todo el dibujo y el manejo de eventos.

Como ejemplo, he escrito un pequeño subprograma de plantilla, llamado Example1Applet, que ilustra el esquema general de un subprograma de animación. Example1Applet muestra cómo crear un hilo y llamar al repaint()método a intervalos fijos. El número de fotogramas por segundo se especifica pasando un parámetro de subprograma. A continuación, se muestra un ejemplo de lo que pondría en su documento HTML:


  

Aquí está Example1Applet.

Nota:

Este subprograma todavía no dibuja nada en la pantalla. El dibujo de la pantalla se explica más adelante. Tenga en cuenta también que el subprograma destruye su hilo de animación cada vez que el usuario abandona la página (lo que hace que stop()se llame al método del subprograma ). Esto asegura que el subprograma no desperdiciará tiempo de CPU mientras su página no esté visible.

Mantener una velocidad de fotogramas constante

En el ejemplo anterior, el subprograma simplemente permanece inactivo durante un período de tiempo fijo entre fotogramas. Esto tiene el inconveniente de que a veces esperas demasiado. Para obtener 10 fotogramas por segundo, no debe esperar 100 milisegundos entre fotogramas, porque pierde algo de tiempo simplemente ejecutando el hilo.

El siguiente subprograma, Example2Applet, muestra cómo mantener un mejor tiempo. Simplemente calcula el retraso correcto entre fotogramas al realizar un seguimiento del tiempo de inicio. Calcula el retardo necesario estimado entre fotogramas en función del tiempo actual.

Aquí está Example2Applet.

Pintar cada cuadro

Lo que queda es pintar cada cuadro. En los ejemplos anteriores, llamamos repaint()a cada marco, lo que hace paint()que se llame al método del applet . Example3Applet tiene un paint()método que dibuja el número del fotograma actual en la pantalla.

Aquí está Example3Applet en acción, seguido de una lista de código.

Nota:

Si especifica que la velocidad de fotogramas sea muy alta (digamos 100 fotogramas por segundo), el run()método llamará repaint()100 veces por segundo. Sin embargo, esto no siempre dará como resultado 100 llamadas a paint()por segundo porque cuando emite una solicitud de repintado demasiado rápido, se contraerán en una sola actualización de pantalla. Es por eso que realizamos un seguimiento del número de fotograma actual en el run()método en lugar de hacerlo en el paint()método.

Generando gráficos

Ahora animemos algo que sea un poco más difícil de dibujar. Example4Applet dibuja una combinación de ondas sinusoidales. Para cada coordenada x, dibuja una línea vertical corta. Todas estas líneas juntas forman un gráfico simple que cambia para cada cuadro. Desafortunadamente, encontrará que este enfoque provoca mucho parpadeo. Explicaremos la causa del parpadeo y algunos remedios en la siguiente sección.

Aquí está Example4Applet en acción, seguido de una lista de código.

Evitar el parpadeo excesivo

El parpadeo que ve en Example4Applet tiene dos causas: pintar cada cuadro lleva demasiado tiempo (debido a la cantidad de cálculo que se requiere durante el repintado) y todo el fondo se borra antes de paint()llamar. Mientras se realiza el cálculo del siguiente cuadro, el usuario ve el fondo de la animación.

Este breve tiempo entre la limpieza del fondo y la pintura de la onda sinusoidal se ve como un destello. En algunas plataformas como la PC, el parpadeo es más obvio que en X Windows. La razón es que los gráficos de X Windows están almacenados en búfer, lo que hace que el flash sea un poco más corto.

Puede reducir el parpadeo en gran medida con dos trucos simples: implementar el update()método y usar doble búfer (a veces conocido como uso de backbuffer ).

Anulando el método update ()

Cuando el AWT recibe una solicitud de repintado para un subprograma, llama al update()método del subprograma . De forma predeterminada, el update()método borra el fondo del subprograma y luego llama al paint()método. Al anular el update()método para incluir el código de dibujo que solía estar en el paint()método, evitamos que se borre toda el área del subprograma con cada repintado.

Ahora que el fondo ya no se borra automáticamente, debemos hacerlo nosotros mismos en el update()método. Ahora podemos borrar cada línea vertical del gráfico individualmente antes de dibujar la nueva línea, eliminando el parpadeo por completo. Este efecto se muestra en Example5Applet.

Aquí está Example5Applet en acción, seguido de una lista de código.

Nota:

Siempre que anule el update()método, aún necesita implementar paint(). Esto se debe a que el paint()sistema de dibujo AWT llama directamente al método siempre que se produce un "daño" en el área de dibujo del subprograma, por ejemplo, cuando una ventana que oculta parte del área de dibujo del subprograma se elimina de la pantalla. Su paint()implementación puede simplemente llamar update().

Doble búfer

Otra forma de reducir el parpadeo entre fotogramas es utilizar doble búfer. Esta técnica se utiliza en muchos subprogramas de animación.

El principio general es que crea una imagen fuera de la pantalla, dibuja un marco en la imagen y luego coloca la imagen completa en la pantalla con una llamada a drawImage(). La ventaja es que la mayor parte del dibujo se realiza fuera de la pantalla. La pintura final de la imagen fuera de la pantalla en la pantalla suele ser mucho más eficiente que pintar el marco directamente en la pantalla.

El subprograma de onda sinusoidal con doble búfer se muestra en Example6Applet. Verá que la animación es bastante fluida y no necesita ningún truco especial al dibujar el marco. La única desventaja es que debe asignar una imagen fuera de la pantalla que sea tan grande como el área de dibujo. Si el área de dibujo es muy grande, esto puede requerir mucha memoria.

Aquí está Example6Applet en acción, seguido de una lista de código.

Nota:

Cuando usa doble búfer, necesita anular el update()método, ya que no desea que se borre el fondo del subprograma antes de pintar el marco. (Usted mismo borra el fondo dibujando la imagen fuera de la pantalla).

Usando imágenes

Ahora reescribiremos el paintFrame()método con un método que anima algunas imágenes. Esto agrega algunas complicaciones menores al problema. Las imágenes son bastante grandes y se cargan de forma incremental. Las imágenes pueden tardar mucho en dibujarse por completo, especialmente cuando las está cargando a través de una conexión lenta. Esta es la razón por la que el drawImage()método toma un cuarto argumento, un objeto ImageObserver. El observador de imágenes es un objeto al que se le notifica cuando llegan más datos de la imagen. Para obtener las imágenes usamos el getImage()método.

Mover una imagen por la pantalla

Este primer subprograma de animación de imágenes, Example7Applet, usa las siguientes dos imágenes:

world.gif: car.gif:

La imagen del mundo se utiliza como fondo y la imagen del automóvil se dibuja encima dos veces, creando una animación de dos autos corriendo por el mundo.

Aquí está Example7Applet en acción, seguido de una lista de código.

Visualización de una secuencia de imágenes

Example8Applet muestra cómo crear una animación usando imágenes separadas para cada cuadro. Aquí están los 10 marcos que se están utilizando:

T1.gif: T2.gif: T3.gif: T4.gif: T5.gif:

T6.gif:

T7.gif:

T8.gif:

T9.gif:

T10.gif:

Seguimos utilizando el doble búfer para eliminar el parpadeo. La razón es que cada imagen que estamos renderizando es parcialmente transparente y, por lo tanto, debemos borrar cada fotograma antes de dibujar el siguiente. Esto causaría flasheo sin doble búfer.

Aquí está Example8Applet en acción, seguido de una lista de código.

Nota:

Al mostrar secuencias de imágenes, debe tener cuidado de alinear las imágenes correctamente. La forma más sencilla es asegurarse de que todas las imágenes sean del mismo tamaño y se puedan dibujar en la misma posición. Si ese no es el caso, su applet tendrá que dibujar cada fotograma con un desplazamiento diferente.

Usar MediaTracker para evitar la visualización incremental

Cuando un programa Java carga una imagen, puede mostrar la imagen antes de que se cargue por completo. El usuario ve que la imagen se renderiza primero de forma incompleta y luego de forma incremental cada vez más completa a medida que se carga la imagen. Esta pantalla incremental proporciona comentarios al usuario (mejorando el rendimiento percibido) y permite que el programa realice fácilmente otras tareas mientras se carga la imagen.

En lo que respecta a la animación, la visualización de imágenes incrementales puede ser útil para las imágenes de fondo, pero puede distraer mucho cuando se utiliza para las imágenes animadas. Por lo tanto, a veces es conveniente esperar hasta que se cargue toda la animación antes de mostrarla.

Puede usar la MediaTrackerclase de Jim Graham para rastrear la descarga de imágenes, retrasando la visualización de la animación hasta que todo el conjunto de imágenes se haya descargado por completo. Example9Applet muestra cómo usar la MediaTrackerclase para descargar imágenes para la animación de Duke.

Aquí está Example9Applet en acción, seguido de una lista de código.

Añadiendo sonido

Es fácil agregar sonido a una animación. Puede utilizar el getAudioClip()método para obtener un objeto AudioClip. Más tarde, puede reproducir el clip como un bucle continuo o como un solo sonido. Example10Applet muestra cómo reproducir un sonido de fondo continuo, así como un sonido repetitivo durante la animación.

Aquí está Example10Applet en acción, seguido de una lista de código.

Nota:

Cuando reproduzca un sonido continuo, debe recordar detenerlo cuando el usuario abandone la página (es decir, hacerlo en el stop()método de su applet ).

Otra nota:

El audio continuo puede resultar muy molesto. Es una buena idea proporcionar al usuario una forma de apagar el audio sin salir de la página. Puede proporcionar un botón o simplemente apagar el audio cuando el usuario hace clic en el subprograma.

Consejos para cargar imágenes más rápido

Una animación que usa muchas imágenes tardará mucho en descargarse. Esto se debe principalmente al hecho de que se realiza una nueva conexión HTTP para cada archivo de imagen, y realizar una conexión puede llevar varios segundos incluso cuando hay mucho ancho de banda.

En esta sección, le informaremos sobre dos formatos de imagen que su subprograma puede usar para agilizar la descarga de imágenes.

Usando una tira de imagen

Puede mejorar el rendimiento de la descarga utilizando una sola imagen que contenga varios fotogramas de animación. Puede renderizar un solo fotograma de la imagen utilizando el clipRect()operador. A continuación se muestra un ejemplo de una tira de imagen que se utiliza en el subprograma UnderConstruction.

El subprograma crea un efecto de perforación al no borrar los fotogramas anteriores. El fondo se borra solo de vez en cuando.

Aquí está UnderConstruction en acción, con un enlace a su código fuente.

Compresión entre cuadros con Flic

Si realmente desea mejorar el rendimiento de descarga de una animación que consta de varios fotogramas, debe utilizar alguna forma de compresión entre fotogramas.

Herramientas de animación

En este momento (enero de 1996), hay pocas herramientas disponibles para ayudarlo a crear animaciones basadas en Java. La mejor herramienta que pude encontrar es The Easy Animator (TEA) de DimensionX (anteriormente conocido como JAM). Te permite crear animaciones de forma interactiva. Nos gustaría animar a los desarrolladores a que escriban más herramientas para la creación de animaciones en Java.

Si tiene algunas imágenes listas para mostrar, puede usar el subprograma Animator. Animator tiene muchos parámetros que le permiten especificar sonidos continuos, sonidos específicos de fotogramas, tiempos y posiciones de fotogramas individuales, una imagen de inicio, orden de fotogramas, etc.

También debe consultar la página de animación de Gamelan para encontrar muchos subprogramas que usan animación.

Conclusión

Espero que este artículo ayude a los desarrolladores de applets a escribir más y mejores applets de animación. También espero que pronto estén disponibles mejores herramientas.

Arthur van Hoff era, hasta hace poco, un ingeniero senior de Sun Microsystems y ha estado involucrado en el desarrollo del lenguaje Java desde 1993. Es el autor del primer compilador de Java escrito íntegramente en Java. Recientemente dejó Sun para formar una nueva empresa junto con Sami Shaio, Kim Polese y Jonathan Payne. La nueva empresa se centrará en la creación de aplicaciones Java. Kathy Walrath es redactora técnica en Sun Microsystems. Ha sido parte del equipo de Java desde 1993. Actualmente, está trabajando con Mary Campione en The Java Tutorial: Object-Oriented Programming para Internet, un tutorial mejorado con subprogramas para aprender el lenguaje Java, la programación de subprogramas y la programación de GUI de Java. . Además de estar disponible en línea, The Java Tutorial también se publicará este verano como parte de Addison-Wesley Java Series.

Esta historia, "Animación en subprogramas de Java", fue publicada originalmente por JavaWorld.