Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Game Development
  2. Programming

Hacer un Splash con efectos dinámicos de agua 2D

by
Read Time:14 minsLanguages:

Spanish (Español) translation by Claudia Márquez (you can also view the original English article)

¡Sploosh! En este tutorial, te mostraré cómo puedes usar matemática simple, física y efectos de partículas para simular ondas y gotas de agua en 2D de gran apariencia.



Nota: Aunque este tutorial está escrito con C# y XNA, debería poder usar las mismas técnicas y conceptos en casi cualquier entorno de desarrollo de juegos.


Vista previa del resultado final

Si tiene XNA, puede descargar los archivos de origen y compilar la demostración por sí mismo. De lo contrario, echa un vistazo al siguiente video de demostración:

Hay dos partes en su mayoría independientes a la simulación de agua. Primero, haremos las olas usando un modelo de primavera. Segundo, usaremos efectos de partículas para agregar salpicaduras.


Haciendo las olas

Para hacer las olas, modelaremos la superficie del agua como una serie de manantiales verticales, como se muestra en este diagrama:

Esto permitirá que las olas suban y bajen. Luego haremos que las partículas de agua atraigan a sus partículas vecinas para permitir que las ondas se propaguen.

Muelles y ley de Hooke

Una gran cosa acerca de los resortes es que son fáciles de simular. Los manantiales tienen una cierta longitud natural; Si estira o comprime un resorte, intentará volver a esa longitud natural.

La fuerza proporcionada por un resorte está dada por la Ley de Hooke:

\[
F = - kx
\]

F es la fuerza producida por el resorte, k es la constante del resorte y x es el desplazamiento del resorte desde su longitud natural. El signo negativo indica que la fuerza está en la dirección opuesta a la que se desplaza el resorte; Si empuja el resorte hacia abajo, lo empujará hacia arriba y viceversa.

La constante de resorte, k, determina la rigidez del resorte.

Para simular los resortes, debemos averiguar cómo mover las partículas en función de la Ley de Hooke. Para hacer esto, necesitamos un par de fórmulas más de la física Primero, la segunda ley de movimiento de Newton:

\[
F = ma
\]

Aquí, F es fuerza, m es masa y a es aceleración. Esto significa que cuanto más fuerte es la fuerza que empuja un objeto, y cuanto más ligero es el objeto, más acelera.

La combinación de estas dos fórmulas y la reorganización nos da:

\[
a = - \ frac {k} {m} x
\]

Esto nos da la aceleración de nuestras partículas. Asumiremos que todas nuestras partículas tendrán la misma masa, por lo que podemos combinar k/m en una sola constante.

Para determinar la posición de la aceleración, necesitamos hacer una integración numérica. Vamos a utilizar la forma más simple de integración numérica: en cada fotograma simplemente hacemos lo siguiente:

Esto se llama el método de Euler. No es el tipo más preciso de integración numérica, pero es rápido, simple y adecuado para nuestros propósitos.

Juntándolo todo, nuestras partículas de la superficie del agua harán lo siguiente en cada cuadro:

Aquí, TargetHeight es la posición natural de la parte superior del resorte cuando no está ni estirado ni comprimido. Debe establecer este valor en el lugar donde desea que esté la superficie del agua. Para la demostración, lo puse en la mitad de la pantalla, a 240 píxeles.

Tensión y efecto humectante

Mencioné anteriormente que la constante de resorte, k, controla la rigidez del resorte. Puedes ajustar este valor para cambiar las propiedades del agua. Una baja constante de resorte hará que los resortes se suelten. Esto significa que una fuerza causará ondas grandes que oscilan lentamente. Por el contrario, una alta constante de resorte aumentará la tensión en el resorte. Las fuerzas crearán pequeñas olas que oscilarán rápidamente. Una alta constante de manantial hará que el agua se vea más como jalea de gelatina.

Una advertencia: no establezca la constante de resorte demasiado alta. Los resortes muy rígidos aplican fuerzas muy fuertes que cambian enormemente en muy poco tiempo. Esto no funciona bien con la integración numérica, que simula los resortes como una serie de saltos discretos en intervalos de tiempo regulares. Un resorte muy rígido puede incluso tener un período de oscilación más corto que el paso de tiempo. Peor aún, el método de integración de Euler tiende a ganar energía a medida que la simulación se vuelve menos precisa, lo que hace que los resortes rígidos exploten

Hay un problema con nuestro modelo de primavera hasta ahora. Una vez que un resorte comienza a oscilar, nunca se detendrá. Para solucionar esto debemos aplicar un poco de humidificación. La idea es aplicar una fuerza en la dirección opuesta a la que se mueve nuestro resorte para reducir la velocidad. Esto requiere un pequeño ajuste a nuestra fórmula de primavera:

\[
a =-\frac{k}{m} x - dv
\]

Aquí, v es la velocidad y d es el factor de amortiguación, otra constante que puede modificar para ajustar la sensación del agua. Debería ser bastante pequeño si quieres que tus ondas oscilen. La demo utiliza un factor de amortiguación de 0.025. Un alto factor de amortiguación hará que el agua se vea espesa como la melaza, mientras que un valor bajo permitirá que las olas oscilen por un largo tiempo.

Haciendo que las ondas se propaguen

Ahora que podemos hacer un manantial, usémoslo para modelar el agua. Como se muestra en el primer diagrama, estamos modelando el agua usando una serie de manantiales verticales paralelos. Por supuesto, si los resortes son todos independientes, las olas nunca se extenderán como lo hacen las olas reales.

Primero mostraré el código y luego lo revisaré:

Este código se llamaría cada fotograma de su método Update(). Aquí, los springs son una serie de resortes, dispuestos de izquierda a derecha. leftDeltas es un conjunto de flotadores que almacena la diferencia de altura entre cada resorte y su vecino izquierdo. rightDeltas es el equivalente para los vecinos correctos. Almacenamos todas estas diferencias de altura en las matrices porque las dos últimas declaraciones if modifican la altura de los resortes. Tenemos que medir las diferencias de altura antes de modificar cualquiera de las alturas.

El código comienza ejecutando la Ley de Hooke en cada primavera como se describió anteriormente. Luego observa la diferencia de altura entre cada resorte y sus vecinos, y cada resorte tira de sus resortes vecinos hacia sí mismo al alterar las posiciones y velocidades de los vecinos. El paso de extracción del vecino se repite ocho veces para permitir que las ondas se propaguen más rápido.

Hay un valor más tweakable aquí llamado Spread. Controla qué tan rápido se propagan las olas. Puede tomar valores entre 0 y 0.5, con valores más grandes que hacen que las ondas se extiendan más rápido.

Para comenzar a mover las olas, vamos a agregar un método simple llamado Splash().

Cuando quieras hacer olas, llama a Splash(). El parámetro de index determina en qué resorte debe originarse la salpicadura, y el parámetro de speed determina qué tan grandes serán las olas.

Representación (Rendering)

Usaremos la clase XNA PrimitiveBatch de XNA PrimitivesSample. La clase PrimitiveBatch nos ayuda a dibujar líneas y triángulos directamente con la GPU. Lo usas así:

Una cosa a tener en cuenta es que, de forma predeterminada, debe especificar los vértices de triángulos en el orden de las agujas del reloj. Si los agrega en el orden contrario a las agujas del reloj, el triángulo se eliminará y no lo verá.

No es necesario tener un resorte para cada píxel de ancho. En la demostración utilicé 201 resortes distribuidos en una ventana de 800 píxeles de ancho. Eso da exactamente 4 píxeles entre cada resorte, con el primer resorte en 0 y el último en 800 píxeles. Probablemente podrías usar incluso menos manantiales y aún así hacer que el agua se vea suave.

Lo que queremos hacer es dibujar trapecios delgados y altos que se extiendan desde la parte inferior de la pantalla hasta la superficie del agua y conecten los resortes, como se muestra en este diagrama:

Como las tarjetas gráficas no dibujan trapezoides directamente, tenemos que dibujar cada trapecio como dos triángulos. Para que se vea un poco más agradable, también haremos que el agua se oscurezca a medida que se profundiza al colorear los vértices inferiores de color azul oscuro. La GPU interpolará automáticamente los colores entre los vértices.

Aquí está el resultado:


Haciendo las salpicaduras

Las olas se ven bastante bien, pero me gustaría ver un chapoteo cuando la roca golpea el agua. Los efectos de partículas son perfectos para esto.

Efectos de partículas

Un efecto de partículas utiliza una gran cantidad de partículas pequeñas para producir algún efecto visual. A veces se usan para cosas como humo o chispas. Vamos a usar partículas para las gotas de agua en las salpicaduras.

Lo primero que necesitamos es nuestra clase de partículas:

Esta clase solo tiene las propiedades que una partícula puede tener. A continuación, creamos una lista de partículas.

En cada cuadro, debemos actualizar y dibujar las partículas.

Actualizamos las partículas para que caigan bajo la gravedad y configuramos la orientación de la partícula para que coincida con la dirección en la que va. Luego nos deshacemos de cualquier partícula que esté fuera de la pantalla o debajo del agua copiando todas las partículas que queremos mantener en una nueva lista y asignándolas a las partículas. A continuación dibujamos las partículas.

A continuación se muestra la textura que utilicé para las partículas.

Ahora, cada vez que creamos una salpicadura, hacemos un montón de partículas.

Puedes llamar a este método desde el método Splash() que usamos para hacer olas. La velocidad del parámetro es qué tan rápido la roca golpea el agua. Haremos salpicaduras más grandes si la roca se mueve más rápido.

GetRandomVector2(40) devuelve un vector con una dirección aleatoria y una longitud aleatoria entre 0 y 40. Queremos agregar un poco de aleatoriedad a las posiciones para que las partículas no aparezcan todas en un solo punto. FromPolar() devuelve un Vector2 con una dirección y longitud dadas.

Aquí está el resultado:

Usando Metaballs como Partículas

Nuestras salpicaduras parecen bastante decentes, y algunos juegos geniales, como World of Goo, tienen salpicaduras de efectos de partículas que se parecen mucho a las nuestras. Sin embargo, te mostraré una técnica para hacer que las salpicaduras se vean más líquidas. La técnica es el uso de metaballs, burbujas de aspecto orgánico sobre las que he escrito un tutorial. Si está interesado en los detalles sobre los metaballs y cómo funcionan, lea ese tutorial. Si solo quieres saber cómo aplicarlos a nuestras salpicaduras, sigue leyendo.

Metaballs se ve como un líquido en la forma en que se fusionan, por lo que son un buen partido para nuestras salpicaduras de líquidos. Para hacer los metaballs, necesitaremos agregar nuevas variables de clase:

Que inicializamos como tal:

Luego dibujamos los metaballs:

El efecto metaball depende de tener una textura de partículas que se desvanece a medida que te alejas del centro. Esto es lo que usé, establecido sobre un fondo negro para hacerlo visible:

Esto es lo que parece:

Las gotas de agua ahora se fusionan cuando están cerca. Sin embargo, no se fusionan con la superficie del agua. Podemos solucionar esto agregando un gradiente a la superficie del agua que lo haga desaparecer gradualmente, y convirtiéndolo en nuestro objetivo de procesamiento metaball.

Agregue el siguiente código al método anterior antes de la línea GraphicsDevice.SetRendertarget(null):

Ahora las partículas se fusionarán con la superficie del agua.

Añadiendo el efecto de biselado

Las partículas de agua se ven un poco planas, y sería bueno darles un poco de sombra. Idealmente, harías esto en un shader. Sin embargo, por el simple hecho de mantener este tutorial simple, usaremos un truco rápido y fácil: simplemente dibujaremos las partículas tres veces con diferentes tintados y compensaciones, como se ilustra en el diagrama a continuación.

Para hacer esto, queremos capturar las partículas metaball en un nuevo objetivo de procesamiento. Luego dibujaremos ese objetivo de renderizado una vez para cada tinte.

Primero, declare un nuevo RenderTarget2D tal como hicimos para las metaballs:

Luego, en lugar de dibujar metaballsTarget directamente en el backbuffer, queremos dibujarlo en partículasTarget. Para hacer esto, vaya al método donde dibujamos los metaballs y simplemente cambie estas líneas:

... para:

Luego use el siguiente código para dibujar las partículas tres veces con diferentes matices y compensaciones:


Conclusión

Eso es todo para agua 2D básica. Para la demostración, agregué una roca que puedes tirar al agua. Extraigo el agua con algo de transparencia sobre la roca para que se vea como si estuviera bajo el agua, y la hago más lenta cuando está bajo el agua debido a la resistencia al agua.

Para hacer que la demostración se vea un poco mejor, fui a opengameart.org y encontré una imagen para la roca y un fondo de cielo. Puede encontrar la roca y el cielo en http://opengameart.org/content/rocks y opengameart.org/content/sky-backdrop respectivamente.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.