Efecto de nieve en Actionscript 3 para Flash

No hay nada más típico en estas fechas con Flash que crear banners, anuncios y demás con efectos de nieve.

La mayoría de veces se trata de una simple secuencia de fotogramas que se va repitiendo, y el efecto puede ser logrado, pero si te fijas bien acabas reconociendo el patrón de repetición. Pero para eso tiene Flash a Actionscript 3, para realizar efectos de nieve más realistas, añadiendo aleatoriedad.

He querido crear mi propio efecto de nieve mediante código Actionscript 3. Pero además de manejar los factores típicos de los copos de nieve, le he querido añadir otras variables, como la influencia del viento, y que el usuario pueda controlar la intensidad de la nevada.

Analizando cómo realizarlo, decidí crear una clase llamada Copo que se controlara a sí misma, de forma que toda su funcionalidad esté integrada en la clase, y una clase Viento, que sería estática puesto que no necesitaría varias instancias de dicha clase. El resultado es este:

Lo primero que realicé fue el gráfico del árbol. Lo realicé en Illustrator con la ayuda de la fusión y el efecto curvar para crear la rama, que luego repetiría. La animación de texto navideño la realicé mediante fotogramas e interpolaciones. También creé un símbolo de clip de película “copo de nieve”, en cuyas propiedades le asigné la clase Copo, exportándolo para Actionscript, y cambiando su clase base de MovieClip a Sprite dado que no necesito línea de tiempo para los copos. Ahora pasaré a comentar la animación de la nieve.

Código

Para evitar tener que escribir código en el archivo FLA, creo un nuevo archivo main.as, y en el fotograma 1 del archivo FLA símplemente le añado esto:

include "main.as";

Este es el código en el archivo main.as:

import flash.display.Sprite;
import flash.utils.Timer;
import flash.events.TimerEvent;
import fl.controls.Slider;
import Viento;

// nivel: Intensidad de la nevada (numero de copos). El valor lo toma del Slider
var nivel:Number = controlNevada.value;
// speed: velocidad de caída general de los copos
var speed:uint = 2;
// tiempoEntreCopos: tiempo de creación entre copo y copo
var tiempoEntreCopos:uint = 200;
// tempCopos: temporizador para creación de copos
var tempCopos:Timer;

// Control de los copos
inicializarNevada();

function inicializarNevada():void {
    tempCopos = new Timer(uint(tiempoEntreCopos / nivel));
    tempCopos.addEventListener(TimerEvent.TIMER, nuevoCopo);
    tempCopos.start();
}

// Inicializar el viento
Viento.inicializar();

// Función que crea nueva instancia de clase Copo
function nuevoCopo(e:TimerEvent):void {
    var copo:Copo = new Copo(this, speed);
    copo.addEventListener(Copo.FIN_COPO, destruirCopo);
}

function destruirCopo(e:Event):void {
    var copo:Sprite = e.target as Sprite;
    copo = null;
}

// Detector para cambios en el control
controlNevada.addEventListener(Event.CHANGE, cambioNevada);

function cambioNevada(e:Event):void {
    tempCopos.removeEventListener(TimerEvent.TIMER, nuevoCopo);
    tempCopos = null;
    nivel = controlNevada.value;
    inicializarNevada();
}

El primer bloque realiza las importaciones necesarias, mientras que el segundo, inicializa variables. Una de ellas, nivel, toma el valor del valor actual del componente Slider de la parte superior derecha. Seguidamente se inicializa la nevada. Lo que ejecuta esta inicialización es la creación de un temporizador cuya frecuencia se verá influída por el valor del Slider. Se le asigna un detector al temporizador para que detecte cada intervalo de la frecuencia, y se pone en marcha. De esta forma, en cada intervalo se ejecuta la función nuevoCopo(), que crea una nueva instancia de la clase Copo, y le asigna un detector para un evento personalizado de esta clase llamado finCopo que se encarga de destruir esa instancia del Copo. Por otro lado tenemos el detector asignado al Slider, de manera que cada vez que el usuario cambie su valor, se ejecuta la función cambioNevada(), que actualiza el temporizador al nuevo valor. Y por último en este documento AS queda la llamada al método de inicialización de la clase Viento, que influirá en el movimiento de los copos de nieve, como se verá posteriormente.

Ahora pasamos a la clase Viento, la cual irá también a intervalos dados creando un empuje horizontal para los copos de nieve. Este es el código:

package {

	import flash.utils.Timer;
	import flash.events.TimerEvent;

	public class Viento {

		private static const VIENTO_MAX = .5;
		private static const TIEMPO_ENTRE_VIENTOS = 1000;
		private static const TIEMPO_MAX = 5000;

		private static var _fuerza:Number;
		private static var _direccion:int;
		private static var _tiempoViento:Timer;

		public static function inicializar():void {
			_fuerza = 0;
			_direccion = 1;
			_tiempoViento = new Timer(TIEMPO_ENTRE_VIENTOS);
			_tiempoViento.addEventListener(TimerEvent.TIMER, cambiarViento);
			_tiempoViento.start();
		}

		private static function cambiarViento(e:TimerEvent):void {
			// Aumentar o disminuir la fuerza del viento mediante un factor aleatorio
			_fuerza += Math.random() / 10 *_direccion;
			// Si la fuerza del viento sobrepasa la fuerza máxima, cambiar de dirección.
			if (Math.abs(_fuerza) > VIENTO_MAX) {
				_direccion *= -1;
			} else if (Math.random() < .5) {
			// Añadir un factor de cambio de dirección aleatorio, solo en caso de que no se sobrepase
			// la velocidad máxima
				_direccion *= -1;
			}
			// Añadir un retardo aleatorio al temporizador
			_tiempoViento.delay = TIEMPO_MAX * Math.random();
		}

		public static function get fuerza():Number {
			return _fuerza;
		}
	}
}

En esta clase hay varios factores aleatorios, para darle al aire un efecto más realista. Uno de ellos es la cantidad de fuerza a añadir (o quitar) al viento cada vez que llega un intervalo del temporizador del viento. Otro, la posibilidad de cambio de dirección también tiene aleatoriedad. El tercer factor es un retardo aleatorio que le he añadido al propio temporizador, para crear intervalos irregulares. Pero vamos paso a paso.

Lo primero son las declaraciones de variables y constantes:

  • VIENTO_MAX: Constante que determina el límite de fuerza x (horizontal) del viento. Si la sobrepasa, tanto en positivo como en negativo, la dirección del viento cambiará.
  • TIEMPO_ENTRE_VIENTOS: El intervalo para el temporizador del viento.
  • TIEMPO_MAX: El valor al que se le aplicará un factor aleatorio que servirá de retardo para el temporizador. Este valor cambiará cada intervalo. De esta forma los intervalos serán irregulares.
  • _fuerza: Se trata de la propiedad que almacena la intensidad del viento. Esta es la propiedad importante dado que es esta la propiedad usada por los copos de nieve.
  • _dirección: Propiedad que almacenará 1 o -1 dependiendo de la dirección del viento.
  • _tiempoViento: El temporizador para el viento.

Una vez declaradas las propiedades y constantes, creamos el método que pone en marcha el viento: inicializar(). Inicialmente la fuerza del viento es cero, es decir, los copos caen verticalmente, en un ángulo de 90 grados, y la dirección es positiva (hacia la derecha). Seguidamente lo que hace este método es crear el temporizador, asignarle un detector y ponerlo en marcha.

Cada intervalo del temporizador se ejecuta el método cambiarViento(), el cual actualiza la fuerza del viento mediante un factor aleatorio. Si dicha fuerza sobrepasa el límite, se cambia la dirección del viento, si no, aplicamos otra probabilidad de que el viento cambie de dirección. Por último, se actualiza el retardo y se aplica al temporizador.

El último método, un getter, entrega el valor de la fuerza del viento actual a quien se lo solicite.

Para conseguir el comportamiento deseado del viento hay que jugar un poco y experimentar. Por ejemplo, para que los cambios de viento no fueran bruscos, tuve que añadirle un divisor por 10 al factor aleatorio de la fuerza. Por otra parte, esta clase la realicé de forma que fuera estática. En Actionscript no se puede crear una clase estática asignando el modificador static a la declaración de clase, pero sí a sus métodos y propiedades. Esta clase no se puede instanciar, por eso no tiene constructor y para inicializarla uso el método inicializar().

Para finalizar, queda por ver el código de la clase Copo.

package {

	import flash.display.Sprite;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	import flash.events.Event;
	import Viento;

	public class Copo extends Sprite {

		private var _padre:Sprite;
		private var _speed:uint;
		private var _temporizador:Timer;
		private var _factorSpeed:Number;

		const FACTOR_CAIDA = 10;

		public static const FIN_COPO = "finCopo";

                // Constructor
		public function Copo(padre:Sprite, speed:uint) {
			_speed = speed;
			_factorSpeed = Math.random() * _speed/2 + .5;
			_padre = padre;
			dimensionar();
			posicionar();
			mover();
		}

                // Aplica una escala aleatoria al copo
		private function dimensionar():void {
			this.scaleY = this.scaleX = .5 + Math.random();
		}

                // Posiciona el copo en la parte superior y en algún lugar aleatorio del eje x.
		private function posicionar():void {
			this.x = _padre.stage.stageWidth * Math.random();
			this.y = -5;
			_padre.addChild(this);
		}

                // Crea el temporizador que aplicará el movimiento del copo
		private function mover():void {
			_temporizador = new Timer(FACTOR_CAIDA);
			_temporizador.addEventListener(TimerEvent.TIMER, caer);
			_temporizador.start();
		}

                // Método que mueve el copo a cada intervalo
		private function caer(e:TimerEvent):void {
			// Mover copo verticalmente
			this.y += _factorSpeed;
			// Movimiento horizontal del viento y el sinuoso de caída de copo
			this.rotation += 1;
			this.x += Math.sin(this.rotation * Math.PI / 180) / 10 + (Viento.fuerza * _factorSpeed);
			// Comprobar si se ha salido por un lateral. Entonces colocarlo en el otro extremo lateral
			if (this.x - this.width / 2 > this.stage.stageWidth) {
				this.x = - this.width / 2;
			} else if (this.x + this.width / 2 < 0) {
				this.x = this.stage.stageWidth + this.width / 2;
			}
			// Comprobar si se ha salido del nivel inferior del escenario,
			// y si es así, destruir copo
			if (this.y - this.width/2 > this.stage.stageHeight) {
				destruir();
			}
		}

                // Método de destrucción del copo una vez llega al suelo.
                // Elimina su temporizador asignado y dispara un evento para que la aplicación lo elimine
		private function destruir():void {
			_temporizador.removeEventListener(TimerEvent.TIMER, caer);
                        _temporizador = null;
			_padre.removeChild(this);
			dispatchEvent(new Event(FIN_COPO));
		}
	}
}

Primero veamos las propiedades y constantes de la clase declaradas:

  • _padre: Se trata del Sprite que contendrá al copo. Se obtiene a través del parámetro del constructor. De esta manera no es necesario asignarlo ni eliminarlo desde el exterior de la lista de visualización de la línea de tiempo principal.
  • _speed: La propiedad que almacena la velocidad general de caída de los copos, cuyo valor también se obtiene por parámetro.
  • _temporizador: El temporizador del copo actual. El cual controlará su movimiento.
  • _factorSpeed: Se trata de una variable obtenida también con un poco de experimentación. A partir del valor de _speed y añadiéndole aleatoriedad, se consigue que cada copo tenga una velocidad de caída algo diferente del resto.
  • FACTOR_CAIDA: intervalo fijo para el movimiento del copo.
  • FIN_COPO: Constante designada para identificar externamente al evento personalizado finCopo.

El constructor realiza secuencialmente diverdad tareas de inicialización: asignación de variables, dimensionamiento y posicionamiento del copo, y puesta en marcha. Como se puede ver, _factorSpeed, que es la variable que aplica el movimiento de caída al copo a cada intervalo, se obtiene a partir de la velocidad general de caída de los copos, más un factor aleatorio. Para obtener el comportamiento que quería también hizo falta algo de expermientación, de manera que no hubiera demasiada diferencia entre los copos rápidos y los lentos, y que a su vez la caída general de todos ellos fuera realista.

El método dimensionar() aplica cierto escalado aleatorio al copo, pero sin que haya demasiada diferencia entre copos grandes y pequeños. El método posicionar() coloca inicialmente el copo en la parte superior del escenario, en algún lugar entre el extremo izquierdo y el derecho. El método mover() crea el temporizador que permitirá los movimientos del copo. Se crea a partir del FACTOR_CAIDA. Cada intervalo del temporizador se ejecutará el método caer().

El método caer() mueve el copo hacia abajo la distancia especificada por _factorSpeed. Seguidamente aplica las fuerzas horizontales, que son dos. La primera es el movimiento sinusoidal del copo cuando cae. Para ello hay que aplicar una rotación al copo, de manera que el seno del ángulo actual provoque los movimientos "contoneantes" para que los copos no caigan como piedras. La segunda fuerza que se le aplica es la del viento. Esto se hace invocando el método getter fuerza() de la clase Viento. Como se trataba de una clase estática, el método se invoca directamente a la clase Viento. Para que el viento no afecte visualmente con igual fuerza a los copos más rápidos y a los más lentos, se le aplica también el factor de velocidad del copo. Esto hace que el viento no parezca demasiado uniforme y da mayor sensación de profundidad. Como digo, es todo cuestión de experimentar y jugar con las variables. También para el movimiento sinuoso, al cual le he tenido que aplicar una división por 10 para que este movimiento no sea tan acusado. A continuación en este mismo método, debido a que los copos tienen movimiento horizontal que puede hacerles desaparecer por un lateral del escenario, hemos de comprobar esta posibilidad, y en el caso de que se cumpla, situar al copo justo en el lateral contrario. Por último en este método, hay que comprobar si el copo actual ha sobrepasado el límite inferior. Ha terminado de caer y debe ser eliminado. En este caso se invoca el método destruir().

El método destruir() no hace otra cosa que eliminar el temporizador, ordenar al padre (línea de tiempo principal) que quite al copo de su vista de visualización, cosa que hará que deje de ser visible, y para finalizar, emite el evento finCopo, que será capturado en la línea de tiempo principal para eliminar cualquier rastro del copo.

Con un poco de experimentación, algo de aleatoriedad, y un toque de trigonometría se puede conseguir un efecto realista de nieve.

One thought on “Efecto de nieve en Actionscript 3 para Flash

  1. Alberto

    Hola, gracias por el tutorial, me ha parecido fantastico para hacer efecto de nieve con flash. Se ve muy realista.

    Saludos!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>