En este tutorial les presentaré el motor de partículas Stardust. Primero, le mostraré cómo configurar Stardust, y luego cubriré las responsabilidades básicas de la clase de Stardust y cómo colaboran juntos para que Stardust funcione en conjunto..
A continuación, veremos el flujo de trabajo general de Stardust y comenzaremos a crear un efecto de partículas con estrellas que salen del cursor del mouse; las estrellas disminuirán gradualmente, crecerán después del nacimiento y se reducirán al morir.
Finalmente, demostraré la flexibilidad de Stardust mediante la creación de varias variaciones del ejemplo ya completo, que incluye el uso de clips de película animados como partículas, la escala de tiempo de simulación de partículas variables y el disparo de objetos de diferentes clases en un solo emisor..
Este tutorial está dirigido a personas que ya están familiarizadas con la programación orientada a objetos (OOP) de ActionScript 3.0, por lo que supongo que ya saben muy bien qué significan las clases, los objetos, la herencia y la interfaz. No hay problema con OOP? Entonces vamos a disparar algunas estrellas!
Como su nombre lo indica, Stardust se utiliza para crear efectos de partículas. Si eres un ActionScripter experimentado, es posible que hayas creado efectos de partículas desde cero muchas veces, y dices: "Soy totalmente bueno con la creación de efectos de partículas desde cero, así que, ¿por qué necesitaría un motor de partículas de todos modos?" Bueno, Stardust está aquí para ayudarlo a enfocarse más en el diseño de comportamiento de partículas real que en preocuparse por las tediosas cosas subyacentes de bajo nivel, como la administración de memoria. En lugar de escribir código para cuidar los datos de partículas, inicializar y desechar recursos, con Stardust, puede omitir estas rutinas aburridas y simplemente decidir cómo desea que se comporten sus partículas..
La estructura de clase de Stardust se inspiró en FLiNT Particle System, otro motor de partículas ActionScript 3.0. Por lo tanto, comparten algunas características básicas similares.
Además de estas funciones básicas, Stardust también proporciona varias funciones avanzadas para usuarios experimentados.
Cuando se trata de efectos de partículas, es muy importante manejar datos masivos de partículas de manera eficiente. Stardust hace un uso intensivo de grupos de objetos y listas vinculadas para mejorar el rendimiento:
Antes de pasar a la codificación real, necesitaremos obtener una copia de Stardust Particle Engine. Se ha publicado bajo la licencia MIT, lo que significa que es totalmente gratuito sin importar si desea usarlo en un proyecto comercial o no comercial..
Aquí está la página de inicio del proyecto de Stardust: http://code.google.com/p/stardust-particle-engine/
Puede descargar Stardust aquí: http://code.google.com/p/stardust-particle-engine/downloads/list
Al momento de escribir este artículo, la versión más reciente que se puede descargar de la lista de descargas es 1.1.132 Beta. Siempre puede obtener la última revisión del repositorio de SVN (que puede no ser estable, sin embargo).
En la página de inicio del proyecto, también puede encontrar más accesorios como documentación de API y una copia del manual en PDF. Incluso hay videos tutoriales en YouTube.
Aquí voy a cubrir brevemente las clases principales de Stardust y sus responsabilidades.
Esta clase es la superclase de todas las clases principales, que define propiedades y métodos especialmente para la serialización XML.
En términos generales, los efectos de partículas tienen que ver con controlar una cantidad de entidades con apariencia y comportamientos similares pero aleatorios. La clase aleatoria es para generar números aleatorios, que se pueden usar en Stardust para aleatorizar propiedades de partículas. Por ejemplo, la clase UniformRandom es una subclase de la clase Random, y su nombre lo dice todo: el número aleatorio generado por un objeto UniformRandom se distribuye uniformemente, y usaré esta clase en particular para todo el tutorial.
Hay ocasiones en que un número aleatorio unidimensional no es suficiente. A veces necesitamos números aleatorios bidimensionales, que son esencialmente pares de números aleatorios, para propiedades como la posición y la velocidad. La clase de zona es para generar pares de números aleatorios bidimensionales. Esta clase modela un par de números aleatorios como un punto aleatorio en una zona 2D. Por ejemplo, CircleZone genera pares de números aleatorios (x, y) a partir de puntos aleatorios dentro de una región circular. Las clases Random y Zone son utilizadas principalmente por la clase Initializer, que se tratará más adelante. La clase Zone3D es la contraparte 3D de esta clase, para efectos de partículas 3D.
La clase de Emisor es básicamente donde se encapsulan todas las cosas de bajo nivel. Un emisor inicializa las partículas recién creadas antes de agregarlas a la simulación, actualiza las propiedades de las partículas en cada iteración del bucle principal y elimina las partículas muertas de la simulación. El método Emitter.step () es lo que desea invocar repetidamente para mantener Stardust en funcionamiento.
La clase Reloj determina la tasa de creación de nuevas partículas para los emisores. Un objeto Emisor contiene exactamente una referencia a un objeto Reloj. Al comienzo de cada llamada al método Emitter.step (), el emisor le pregunta al objeto del reloj cuántas partículas nuevas debe crear. Tome la clase SteadyClock, por ejemplo, le dice a los emisores que creen nuevas partículas a una velocidad constante.
Esta clase es para inicializar partículas recién creadas. Es necesario agregar un objeto Inicializador a un emisor para que funcione. Básicamente, una subclase de inicializador inicializa solo una propiedad de partícula. Por ejemplo, la clase de inicializador de masa inicializa la masa de nuevas partículas. Algunos inicializadores aceptan un objeto aleatorio como parámetro de constructor para inicializar partículas con valores aleatorios. El siguiente código crea un inicializador de vida que inicializa la vida de las partículas a valores centrados en 50 con una variación de 10, es decir, entre el rango de 40 a 60.
nueva vida (nuevo UniformRandom (50, 10));
Los objetos de acción actualizan las propiedades de las partículas en cada iteración del bucle principal (el método Emiter.step ()). Por ejemplo, la clase de acción Mover actualiza las posiciones de las partículas según la velocidad. Un objeto de acción debe agregarse a un emisor para que funcione.
Ahora que sabe cómo colaboran las clases principales, echemos un vistazo a un flujo de trabajo general para Stardust.
Empiezas creando un emisor. Use la clase Emitter2D para efectos de partículas 2D y la clase Emitter3D para efectos 3D.
var emitter: Emitter = new Emitter2D ();
Para especificar la tasa de creación de partículas, necesitamos un reloj. Esto se puede establecer mediante la propiedad Emitter.clock o pasando un reloj como primer parámetro al constructor del emisor.
// enfoque de propiedad emitter.clock = new SteadyClock (1); // enfoque del constructor var emitter: Emitter = new Emitter2D (new SteadyClock (1));
Agregue inicializadores al emisor a través del método Emitter.addInitializer ().
emitter.addInitializer (nueva Vida (nuevo UniformRandom (50, 10))); emitter.addInitializer (nueva Escala (nuevo UniformRandom (1, 0.2)));
Agregue acciones al emisor a través del método Emitter.addAction ().
emitter.addAction (new Move ()); emitter.addAction (new Spin ());
Cree un procesador y agregue el emisor al procesador a través del método Renderer.addEmitter ().
var renderer: Renderer = new DisplayObjectRenderer (contenedor); // "contenedor" es nuestro contenedor sprite renderer.addEmitter (emisor);
Finalmente, llame repetidamente al método Emitter.step () para mantener en funcionamiento la simulación de partículas. Es posible que desee utilizar el evento enter-frame o un temporizador para hacer esto. En una sola llamada del método Emitter.step (), el reloj determina cuántas partículas nuevas deben crearse, las nuevas partículas se inicializan con inicializadores, todas las partículas se actualizan mediante acciones, se eliminan las partículas muertas y, finalmente, el renderizador el efecto partícula.
// enfoque del evento enter-frame addEventListener (Event.ENTER_FRAME, mainLoop); // timer enfoque timer.addEventListener (TimerEvent.TIMER, mainLoop); función mainLoop (e: Evento): void emitter.step ();
Bien. Eso es prácticamente todo para la cartilla de Stardust. Ahora es el momento de abrir el IDE de Flash y ensuciarse las manos.
Cree un nuevo documento de Flash con una dimensión de 640X400, una velocidad de fotogramas de 60 fps y un fondo oscuro. Aquí hice un fondo degradado azul oscuro. Por cierto, Stardust funciona bien con Flash Player 9 y 10, así que está bien, sin importar que uses Flash CS3 o CS4. En este tutorial usaré Flash CS3..
Estamos creando un efecto de partícula con estrellas, por lo que tendremos que dibujar una estrella y convertirla en un símbolo, por supuesto, exportado para ActionScript. Este símbolo se usará más adelante para representar nuestro efecto de partículas. Nombra el símbolo y la clase exportada "Estrella".
Cree una nueva clase de documento y asígnele el nombre StarParticles.
paquete import flash.display.Sprite; clase pública StarParticles extiende Sprite función pública StarParticles ()
Como se mencionó en el flujo de trabajo general, el primer paso es crear un emisor. Y el siguiente paso es agregar inicializadores y acciones al emisor. Si bien esto se puede hacer en el constructor de la clase de documento, recomiendo encarecidamente que se haga en una subclase de Emisor separada. Siempre es mejor separar el diseño de comportamiento de partículas del programa principal; Al hacerlo, el código es mucho más limpio y fácil de modificar en el futuro, sin confundirse con el programa principal..
Vamos a crear un efecto de partículas 2D, por lo que Emitter2D es la clase de emisor que vamos a extender. Amplíe la clase Emitter2D y asígnele el nombre StarEmitter, ya que haremos que dispare estrellas más tarde. El constructor Emisor acepta un parámetro Reloj, por lo que declararemos un parámetro constructor para pasar una referencia de objeto Reloj al constructor de la superclase.
paquete import idv.cjcat.stardust.twoD.emitters.Emitter2D; la clase pública StarEmitter extiende Emitter2D public function StarEmitter (clock: Clock) // pasa el objeto de reloj al constructor de la superclase super (clock);
Un mejor enfoque para crear una subclase de emisor es declarar los parámetros de partículas como constantes estáticas, agrupadas en un solo lugar. Entonces, en caso de que desee modificar los parámetros, siempre sabrá dónde encontrar las declaraciones. El significado de estas constantes se explicará más adelante cuando se utilicen..
// promedio de vida útil privada estática const LIFE_AVG: Número = 30; // variación de la vida útil const. estática privada LIFE_VAR: Número = 10; // escala estática privada de la escala promedio SCALE_AVG: Number = 1; // variación de escala const. estática privada SCALE_VAR: Número = 0.4; // escala tiempo de crecimiento privado const. estática GROWING_TIME: Número = 5; // escala tiempo de encogimiento privado const. estática SHRINKING_TIME: Número = 10; // velocidad media estática privada const SPEED_AVG: Number = 10; // variación de velocidad const. estática privada SPEED_VAR: Number = 8; // promedio omega (velocidad angular) const. estática privada OMEGA_AVG: Número = 0; // variación omega constante estática privada OMEGA_VAR: Número = 5; // coeficiente de amortiguamiento privado const. estática DAMPING: Number = 0.1;
¿Qué inicializadores necesitamos para crear nuestro efecto de partículas? Echemos un vistazo a la siguiente lista:
Y aquí está el código:
point = new SinglePoint (); addInitializer (new DisplayObjectClass (Star)); addInitializer (new Life (nuevo UniformRandom (LIFE_AVG, LIFE_VAR))); addInitializer (nueva escala (nuevo UniformRandom (SCALE_AVG, SCALE_VAR))); addInitializer (nueva posición (punto)); addInitializer (nuevo Velocity (nuevo LazySectorZone (SPEED_AVG, SPEED_VAR))); addInitializer (nueva rotación (nuevo UniformRandom (0, 180))); addInitializer (nuevo Omega (nuevo UniformRandom (OMEGA_AVG, OMEGA_VAR)));
Está bien, hemos terminado con los inicializadores. Ahora es el momento de agregar acciones al emisor. A continuación hay una lista de acciones que necesitamos:
Eso es. Nuestro emisor está hecho. Aquí está el código para este emisor en su totalidad, incluidas las declaraciones de importación necesarias.
paquete import idv.cjcat.stardust.common.actions.Age; importar idv.cjcat.stardust.common.actions.DeathLife; importar idv.cjcat.stardust.common.actions.ScaleCurve; importar idv.cjcat.stardust.common.clocks.Clock; importar idv.cjcat.stardust.common.initializers.Life; importar idv.cjcat.stardust.common.initializers.Scale; importar idv.cjcat.stardust.common.math.UniformRandom; importar idv.cjcat.stardust.twoD.actions.Damping; importar idv.cjcat.stardust.twoD.actions.Move; importar idv.cjcat.stardust.twoD.actions.Spin; importar idv.cjcat.stardust.twoD.emitters.Emitter2D; importar idv.cjcat.stardust.twoD.initializers.DisplayObjectClass; importar idv.cjcat.stardust.twoD.initializers.Omega; importar idv.cjcat.stardust.twoD.initializers.Position; importar idv.cjcat.stardust.twoD.initializers.Rotation; importar idv.cjcat.stardust.twoD.initializers.Velocity; importar idv.cjcat.stardust.twoD.zones.LazySectorZone; importar idv.cjcat.stardust.twoD.zones.SinglePoint; clase pública StarEmitter extiende Emitter2D / ** * Constantes * / const. estática privada LIFE_AVG: Número = 30; constata estática privada LIFE_VAR: Número = 10; constata estática privada SCALE_AVG: Número = 1; constata estática privada SCALE_VAR: Número = 0.4; constata estática privada GROWING_TIME: Número = 5; constata estática privada SHRINKING_TIME: Número = 10; constata estática privada SPEED_AVG: Número = 10; constata estática privada SPEED_VAR: Number = 8; constata estática privada OMEGA_AVG: Número = 0; constata estática privada OMEGA_VAR: Número = 5; DAMPING estático privado: Número = 0.1; punto de var público: SinglePoint; función pública StarEmitter (reloj: reloj) super (reloj); point = new SinglePoint (); // inicializadores addInitializer (new DisplayObjectClass (Star)); addInitializer (new Life (nuevo UniformRandom (LIFE_AVG, LIFE_VAR))); addInitializer (nueva escala (nuevo UniformRandom (SCALE_AVG, SCALE_VAR))); addInitializer (nueva posición (punto)); addInitializer (nuevo Velocity (nuevo LazySectorZone (SPEED_AVG, SPEED_VAR))); addInitializer (nueva rotación (nuevo UniformRandom (0, 180))); addInitializer (nuevo Omega (nuevo UniformRandom (OMEGA_AVG, OMEGA_VAR))); // acciones addAction (new Age ()); addAction (nuevo DeathLife ()); addAction (new Move ()); addAction (new Spin ()); addAction (nueva amortiguación (DAMPING)); addAction (nuevo ScaleCurve (GROWING_TIME, SHRINKING_TIME));
Ahora es el momento de volver a la clase de documentos y terminarla. Echemos un vistazo a las tareas restantes..
A continuación se muestra el código completo de la clase de documento, incluidas las declaraciones de importación necesarias.
paquete import flash.display.Sprite; importar flash.display.StageScaleMode; import flash.events.Event; import flash.geom.Rectangle; importar idv.cjcat.stardust.common.clocks.SteadyClock; importar idv.cjcat.stardust.common.renderers.Renderer; importar idv.cjcat.stardust.twoD.renderers.DisplayObjectRenderer; la clase pública StarParticles extiende Sprite private var emitter: StarEmitter; función pública StarParticles () // crear una instancia del emisor de StarEmitter = nuevo StarEmitter (nuevo SteadyClock (0.5)); // el contenedor sprite var container: Sprite = new Sprite (); // el renderizador que representa el renderizador var del efecto de partículas: Renderer = new DisplayObjectRenderer (container); renderer.addEmitter (emisor); // agregar el contenedor a la lista de visualización, sobre el fondo addChildAt (container, 1); // hacer uso del evento enter-frame addEventListener (Event.ENTER_FRAME, mainLoop); función privada mainLoop (e: Evento): void // actualizar la posición de SinglePoint a la posición del mouse emitter.point.x = mouseX; emitter.point.y = mouseY; // llamar al bucle principal emitter.step ();
¡Finalmente, hemos terminado! Ahora echemos un vistazo al resultado. Presione CTRL + ENTRAR en Flash para probar la película y verá el resultado.
¡No hemos terminado todavía! Hagamos algunas variaciones más. El primero es usar clips de película animados para nuestras partículas..
Esta primera variación es bastante simple, no implica ninguna codificación adicional. Es tan simple como crear una animación de línea de tiempo básica. Edite el símbolo de la estrella en Flash IDE, cree otro fotograma clave y cambie el color de la estrella en este marco a rojo. Esto esencialmente hace que las estrellas parpadeen entre amarillo y rojo. Es posible que desee insertar más marcos vacíos en el medio, ya que una velocidad de fotogramas de 60 fps es demasiado rápida para un parpadeo de dos cuadros.
Ahora prueba la película y comprueba el resultado. El efecto de estrella parpadeante parece caricaturesco; esto se puede usar para los efectos clásicos de mareos, que se ven comúnmente en los dibujos animados.
Como mencioné anteriormente, una de las características de Stardust es "escala de tiempo de simulación ajustable", lo que significa que la escala de tiempo utilizada por Stardust para la simulación de partículas se puede ajustar de manera dinámica. Todo se hace cambiando la propiedad Emitter.stepTimeInterval, que es 1 de forma predeterminada. El siguiente fragmento de código cambia este valor a 2, lo que hace que las partículas se muevan dos veces más rápido y que el emisor cree nuevas partículas a doble velocidad..
emitter.stepTimeInterval = 2;
En esta variación, crearemos un control deslizante en el escenario y lo utilizaremos para ajustar dinámicamente la escala de tiempo de la simulación.
Arrastre un componente Slider desde el Panel de componentes al escenario. Nombra "deslizador".
Nos gustaría que el control deslizante se deslice entre 0.5 y 2