Descripción del algoritmo lógico y análisis de un ejemplo de trabajo en forma de juego tecnodemo.
Versión WebGL2 de esta demostración https://danilw.itch.io/flat-maze-web para otros enlaces, vea el artículo.
El artículo está dividido en dos partes, la primera sobre lógica y la segunda sobre aplicación en el juego, la primera parte :
- Características clave
- Enlaces y una breve descripción.
- El algoritmo de la lógica.
- Las limitaciones de la lógica. Errores / características y errores de ángulo.
- Acceso a los datos del índice.
Descripción adicional de la demostración del juego, la segunda parte :
- Características utilizadas de esta lógica. Y representación rápida de un millón de partículas de píxeles.
- Implementación, algunos comentarios sobre el código, descripción de la colisión en dos direcciones. E interacción con el jugador.
- Enlaces a gráficos usados con opengameart y un sombreador para sombras. Y el enlace del artículo a cyberleninka.ru
Parte 1
1. Características clave
La idea es una colisión / física de cientos de miles de partículas entre sí, en tiempo real, donde cada partícula tiene un identificador de identificación único.
Cuando se indexa cada partícula, es posible controlar cualquier parámetro de cualquier partícula , por ejemplo, masa, su salud (hp) o daño, aceleración, desaceleración, qué objetos encontrar y reacciones al evento dependiendo del tipo / índice de la partícula, también temporizadores únicos para cada partícula , y así sucesivamente según sea necesario.
Toda la lógica en GLSL es totalmente portátil para cualquier motor de juego y cualquier sistema operativo que tenga soporte GLES3.
El número máximo de partículas es igual al tamaño del framebuffer (fbo, todos los píxeles).
Un número cómodo de partículas (cuando hay espacio para que las partículas interactúen) es (Resolution.x*Resolution.y/2)/2
es cada segundo píxel en x
cada segundo píxel en y
, por eso la descripción lógica lo dice.
La primera parte del artículo muestra la lógica mínima; en la segunda, usando el ejemplo de un juego, lógica con una gran cantidad de condiciones de interacción.
2. Enlaces y breve descripción
Hice tres demos sobre esta lógica:
1. En fragment-shader GLSL , en shadowrtoy https://www.shadertoy.com/view/tstSz7 , vea el código BufferC en él toda la lógica. Este código también le permite mostrar cientos de miles de partículas con su UV, en una posición arbitraria, en un sombreador de fragmentos sin usar partículas instantaneas.

2. Lógica de portabilidad a partículas instanciadas (utilizada por Godot como motor)
Enlaces Versión web , exe (win) , fuentes del proyecto partículas_2D_self_collision .
Breve descripción: esta es una mala demostración de partículas instantaneas, debido al hecho de que realizo el aumento máximo donde se ve todo el mapa, siempre se procesan 640x360 partículas (230k), esto es mucho. Ver más abajo en la descripción del juego, allí lo hice bien, sin partículas adicionales. (hay un error de índice de partículas en el video, esto se corrige en el código)
3. El juego, sobre esto a continuación en la descripción del juego. Enlaces Versión web , exe (win) , fuentes
3. El algoritmo de la lógica.
Brevemente:
La lógica es similar a la caída de arena, cada píxel conserva el valor fraccional de la posición (desplazamiento dentro de su píxel) y la aceleración actual.
La lógica verifica los píxeles en el radio 1, que su próxima posición quiere ir a este píxel (debido a esta restricción, vea las restricciones a continuación) , también los píxeles en el radio 2 para repulsión (colisión).
El índice único se guarda traduciendo la lógica a int-float y reduciendo el tamaño para la posición de posición dada y la velocidad pos
.
Los datos se almacenan de esta manera: (debido a este error, ver restricciones)
pixel.rgba r=[0xfffff-posx, 0xf-data] g=[0xfffff-posy, 0xf-data] b=[0xffff-velx, 0xff-data] a=[0xffff-vely, 0xff-data]

En el código , números de línea para BufC https://www.shadertoy.com/view/tstSz7 , 115 comprobación de transición, 139 comprobaciones de colisión.
Estos son bucles simples para tomar valores adyacentes. Y la condición es que, si la posición se toma igual a la posición del píxel actual, entonces movemos esos datos a este píxel (debido a esta restricción) , y el valor de vel
cambia dependiendo de los píxeles vecinos, si los hay.
Todo esto es lógica de partículas.
Es mejor colocar las partículas a una distancia de 1 píxel entre sí si están más cerca de 1 píxel, entonces habrá repulsión, como un mapa con un laberinto en el juego, las partículas se colocan en sus lugares sin moverse debido a una distancia de 1 píxel entre ellas.
Luego viene el renderizado (renderizado), en el caso del sombreador de fragmentos, los píxeles se toman en un radio de 1 para mostrar las áreas que se cruzan. En el caso de partículas instanciadas, se toma un píxel en la dirección INSTANCE_ID
traducido de una vista lineal a una matriz bidimensional.
4. Limitaciones de la lógica. Errores / características y errores ANGLE
- El tamaño de píxel ,
BALL_SIZE
en el código, debe estar dentro de los límites para el cálculo, mayor que sqrt(2)/2
y menor que 1
. Cuanto más cerca de 1, menos espacio para caminar dentro del píxel (el píxel mismo), menos espacio hay. Tal tamaño es necesario para que los píxeles no caigan entre sí, se puede establecer menos de 1 cuando tiene objetos pequeños, se crea una ilusión de objetos de menos de 1 píxel (calculado). - La velocidad no puede ser superior a
1
píxel; de lo contrario, los píxeles desaparecerán. Pero para tener una velocidad de más de 1
por cuadro, puede hacerlo, si realiza varios framebuffer (fbo / viewport) y procesa varios pasos lógicos por velocidad de cuadro, aumentará la cantidad de veces igual al número de fbo adicional. Esto es lo que hice en la demostración de frutas, y usando el enlace a shadowrtoy (bufC copiado a bufD). - Limitación de presión (como gravedad u otro mapa de fuerza normal). Si varios píxeles vecinos toman la posición de esto (vea la imagen de arriba), entonces solo se guarda uno, los píxeles restantes desaparecen. Esto es fácil de notar en la demostración en shadowrtoy, configure el mouse en Force, cambie el valor de
MOUSE_F
en común a 10
y dirija las partículas a la esquina de la pantalla, desaparecerán una en la otra. O lo mismo con el valor de gravedad maxG
en común . - Error en ángulo. Para que esta lógica funcione en las partículas de GPU (instanciadas), es mejor (más barato, más rápido) calcular la posición y todos los demás parámetros de partículas para mostrar, por ejemplo, sombreador . Pero Angle no permite el uso de más de una textura fbo para un sombreador, por lo que el cálculo de parte de la lógica debe transferirse al sombreador de vértices donde transferir el número de índice del sombreador de instancia. Esto es lo que hice en ambas demostraciones con partículas de GPU.
- Un error grave en ambas demostraciones (excepto en el juego) el valor de posición se perderá si no es un múltiplo de
1/0xfffff
prueba de error está aquí https://www.shadertoy.com/view/WdtSWS
Más precisamente, esto no es un error, y debería ser así, por simplicidad, como parte de este algoritmo, lo llamé un error.
Fix bug:
No convierta el valor de posición en int-float , debido a esto 0xff
desaparecerá, 8 bits disponibles para datos, pero el valor 0xffff
para datos permanecerá, lo que puede ser suficiente para muchas cosas.
Hice exactamente eso en la demostración del juego , uso solo 0xffff
para los datos donde se almacenan el tipo de partícula, el temporizador de animación, la salud y todavía hay espacio libre.
5. Acceso a los datos del índice.
instancia-partícula tiene su propio INSTANCE_ID
, toma un píxel de la textura del framebuffer con lógica de partículas (bufC, ejemplo para sombreador), si allí desempaquetamos el ID de la partícula (ver almacenamiento de datos) de esta partícula , en este ID leemos la textura con los datos de las partículas (bufB , un ejemplo en un sombreador).
En el ejemplo de shadowtyy, bufB almacena solo el color de cada partícula, pero es obvio que puede haber datos, como masa, aceleración, desaceleración, escritos anteriormente, así como acciones lógicas (por ejemplo, puede mover cualquier partícula a cualquier posición (teletransporte) si está hecho acción lógica correspondiente en el código), también puede controlar el movimiento de cualquier partícula o grupo desde el teclado ...
Quiero decir que puedes hacer cualquier cosa con cada una de las partículas como si fueran partículas ordinarias en una matriz en el procesador, el acceso bidireccional desde la partícula de la GPU puede cambiar su estado, pero también desde la CPU puedes cambiar el estado de la partícula por índice (usando acciones lógicas y textura buffer de datos).
Parte 2
1. Características utilizadas de esta lógica. Y rápido renderizado de un millón de partículas de píxeles
El tamaño del framebuffer (fbo / viewport) para partículas es 1280x720, las partes se encuentran después de 1, esto es 230 mil partículas activas (elementos activos en el laberinto).
Siempre hay no más de 12 mil partículas instaladas en GPU en la pantalla.
Usos lógicos:
- La lógica del jugador es independiente de la lógica de partículas, y solo lee datos del búfer de partículas.
- El jugador se ralentiza cuando choca con objetos.
- Los objetos de tipo monstruo infligen daño al jugador.
- El jugador tiene 2 ataques, uno repele todo, el segundo crea partículas como bola de fuego (la imagen es esta)
- El tipo de bola de fuego tiene su propia masa, y funciona el seguimiento bilateral de colisiones con otras partículas.
- otras partículas, como el lanzamiento y los zombis (un tipo de lanzamiento es invulnerable) se destruyen en una colisión con una bola de fuego
- bola de fuego se apaga después de una colisión
- Niveles de física : el jugador repele árboles y cuadrados, otras partículas no interactúan, ninguna aceleración actúa sobre la bola de fuego
- los temporizadores de animación son únicos para cada partícula
En comparación con la demostración de frutas, donde hay gastos generales, en este juego el número de partículas instaladas en GPU es de solo 12 mil.
Se ve así:

Su número depende del zoom actual ( zoom ) del mapa, y el aumento se limita a un cierto valor, por lo que solo se consideran aquellos que son visibles en la pantalla.
La pantalla cambia con el jugador, la lógica para calcular los cambios es un poco compleja y muy situacional, dudo que encuentre aplicación en otro proyecto.
2. Implementación, algunos comentarios sobre el código.
Todo el código del juego está en la GPU.
La lógica para calcular el desplazamiento de partículas en una pantalla con un aumento en la función de vértice en el archivo /shaders/scene2/particle_logic2.shader es un archivo de sombreador de partículas (vértice y fragmento), no un sombreador instanciado, un sombreador instanciado no hace nada, solo pasa su índice debido a error descrito anteriormente.
partículas por tipo y toda la lógica de la interacción de partículas en un archivo, este es un archivo de un sombreador de marco sombreador de archivos sombreadores / escena2 / partículas_fbo_logic.shader
// 1-2 ghost // 3-zombi // 4-18 blocks // +20 is on fire // 40 is bullet(right) 41 left 42 top 43 down
píxel de almacenamiento de datos [pos.x, pos.y, [0xffff-vel.x, 0xff-data1],[0xffff-vel.y, 0xff-data2]]
data1 es un tipo, data2 es un HP o temporizador.
El temporizador va en cuadros en cada partícula , el valor máximo del temporizador es 255, no necesito tanto, uso solo 1-16 máximo ( 0xf
), y 0xf
permanece sin usar donde, por ejemplo, puede almacenar el valor real de HP, no se usa para mí. (es decir, sí, uso 0xff
para el temporizador , pero de hecho solo tengo menos de 16 cuadros de animación, y 0xf
suficiente, pero no necesitaba datos adicionales)
En realidad, 0xff
solo 0xff
usa en el temporizador de árboles en llamas, se convierten en zombies después de 255 fotogramas. La lógica del temporizador está parcialmente en type_hp_logic
en el sombreador de framebuffer de partículas (enlace de arriba).
Un ejemplo de una operación de colisión bidireccional cuando una bola de fuego se apaga en el primer golpe, y el objeto con el que fue golpeado también realiza su acción.
File shaders / scene2 / roots_fbo_logic.shader line 438:
if (((real_index == 40) || (real_index == 41) || (real_index == 42) || (real_index == 43)) && (type_hp.y > 22)) { int h_id = get_id(fragCoord + vec2(float(x), float(y))); ivec2 htype_hp = unpack_type_hp(h_id); int hreal_index = htype_hp.x; if ((hreal_index != 40) && (hreal_index != 41) && (hreal_index != 42) && (hreal_index != 43)) type_hp.y = 22; } else { if (!need_upd) { int h_id = get_id(fragCoord + vec2(float(x), float(y))); ivec2 htype_hp = unpack_type_hp(h_id); int hreal_index = htype_hp.x; if (((hreal_index == 40) || (hreal_index == 41) || (hreal_index == 42) || (hreal_index == 43)) && (htype_hp.y > 22)) { need_upd = true; } } }
real_index
es un tipo, los tipos se enumeran arriba, 40-43 es una bola de fuego .
Además type_hp.y > 22
es el valor del temporizador, si es mayor que 22, entonces la bola de fuego no encontró nada.
h_id = get_id(...
toma el valor del tipo y HP (temporizador) de la partícula encontrada
hreal_index != 40...
tipo ignorado (otra bola de fuego )
type_hp.y = 22
un temporizador se establece en 22, este es un indicador de que esta bola de fuego colisionó con un objeto.
else { if (!need_upd)
variable need_upd comprueba que no hay colisiones repetidas, ya que la función está en un bucle, nos encontramos con una bola de fuego .
h_id = get_id(...
si aún no hubo una colisión, tomamos un objeto de tipo y temporizador.
hreal_index == 40...htype_hp.y > 22
que el objeto de colisión es bola de fuego y no se apaga.
need_upd = true
indicador de que es necesario actualizar el tipo ya que encontró una bola de fuego .
más línea 481
if((need_upd)&&(real_index<24)){
real_index <24 por tipo menos de 24 hay árboles de zombis y fantasmas que no se queman, y luego, en esta condición, actualizamos el tipo dependiendo del tipo actual.
Por lo tanto, se puede hacer casi cualquier interacción de objetos.
Interacción con el jugador:
File shaders / scene2 / logic.shader line 143 function player_collision
Esta lógica lee los píxeles que rodean al jugador en un radio de 4x4 píxeles, toma la posición de cada uno de los píxeles y lo compara con la posición del jugador, si se encuentra un elemento, entonces la verificación de tipo es la siguiente, si este es un monstruo, tomamos HP del jugador.
Esto funciona un poco impreciso y no quería solucionarlo , esta función se puede hacer más precisa.
Las partículas se alejan del jugador y el efecto de repulsión durante un ataque:
Un framebuffer (viewport) se usa para escribir lo normal de las acciones actuales, y las partículas ( cells_fbo_logic.shader ) toman esta textura (de lo normal) en su posición y aplican el valor a su velocidad y posición. El código completo de esta lógica es literalmente solo un par de líneas, archivo force_collision.shader
Al hacer clic con el botón izquierdo del mouse, vuelan los proyectiles de bola de fuego ; su apariencia no es muy natural , no se corrigieron y se fueron de esta forma.
Puedes hacer una zona normal (forma) para generar partículas con un cambio que aparece en relación con el jugador (esto no se hace).
O puede hacer que la bola de fuego sea un objeto separado como jugador y dibujar normal en un búfer para alejar las partículas de la bola de fuego , es decir, por analogía con el jugador ...
¿Quién necesita pensar que lo resolverán por sí mismos?
3. Enlaces a los gráficos utilizados con opengameart y el sombreador de sombras
Me dieron un enlace a un artículo en cyberleninka.ru
En la descripción del algoritmo que utilicé, quizás haya una descripción más detallada y correcta que en este, mi artículo.
El sombreador de sombras funciona de manera muy simple, basado en este sombreador https://www.shadertoy.com/view/XsK3RR (tengo un código modificado)
Shader construye un mapa de luz radial 1D

y sombreado en el código de pintura del piso shaders / scene2 / mainImage.shader
Enlaces a los gráficos utilizados , todos los gráficos del juego desde el sitio https://opengameart.org
bola de fuego https://opengameart.org/content/animated-traps-and-obstacles
personaje https://opengameart.org/content/legend-of-faune
árboles y bloques https://opengameart.org/content/lolly-set-01
(y un par de fotos más con opengameart)
Los gráficos en el menú fueron obtenidos por el sombreador 2D_GI, una utilidad para crear tales menús:
Quien leyó hasta el final - bien hecho :)
Si tiene preguntas, pregunte, puedo complementar la descripción a pedido.