Encuentra eficientemente a muchos enemigos en bandada alrededor de obstáculos

20

Estoy trabajando para tratar de mejorar la búsqueda de caminos para los enemigos de mi juego. En este momento, básicamente se mueven constantemente hacia la posición exacta del jugador calculando el ángulo entre ellos y los jugadores y moviéndose en esa dirección. También tengo un algoritmo de congregación que evita que los enemigos se apilen uno encima del otro, por lo que se formarán en grupos en lugar de cortarse entre sí.

Sin embargo, ahora que he agregado un mapa basado en mosaicos, necesito que los enemigos también puedan sortear obstáculos y muros, por ejemplo. Inicialmente intenté agregar un valor de separación a las baldosas "no transitables" para que el algoritmo de flocado considerara las paredes y los obstáculos como objetos para alejarse. Todavía tengo que determinar si esto es factible o no porque mi prueba inicial mostró que los enemigos golpean un "muro" invisible donde no hay baldosas no transitables, pero por alguna razón, lo golpean y comienzan a salir.

Me preguntaba si sería demasiado pesado el rendimiento para calcular una ruta al jugador usando A * y luego usar el algoritmo de flocado para evitar la aglomeración. Originalmente, mi juego iba a ser un juego de disparos basado en olas, pero en su lugar decidí hacerlo a nivel de la línea de Hotline Miami, por lo que es probable que tenga menos enemigos, con la horda ocasional, y solo haga ellos más fuertes

¿Es esta una solución viable? Estoy usando Java con Slick2D como mi motor de juego. ¿O hay una mejor solución / algoritmo que aborde estos dos problemas?

Darin Beaudreau
fuente
77
Como describí en la edición, "es demasiado pesado" es una pregunta que debe hacerle a su generador de perfiles, ya que dependerá de su implementación, hardware objetivo, presupuesto de rendimiento y el contexto de su juego, todo lo que usted y su generador de perfiles conocen. íntimamente, pero los extraños de Internet no. Si desea que los rebaños se encuentren de manera eficiente, podemos sugerir estrategias para ayudar con eso, pero solo su propio perfil puede responder lo que es lo suficientemente eficiente para sus necesidades. Si perfila e identifica un problema de rendimiento específico, también podemos ayudarlo a encontrar la manera de resolver ese problema.
DMGregory
1
La forma de implementarlos afecta el rendimiento. Por ejemplo, solo ejecutar A * en líderes y confiar en la congregación de seguidores.
Pikalek
Si su juego se basa principalmente en luchar contra estos enemigos, el algoritmo que tome tendrá un impacto masivo en cómo se siente el juego. Por lo tanto, debe intentar diferentes enfoques, por ejemplo, ¿siente que los enemigos conocen perfectamente el nivel y la posición del jugador en todo momento y lo rastrean como lo indica una IA que lo sabe todo? - otros enfoques podrían ser dejar que los enemigos corran en la dirección general donde el jugador hizo ruido y solo en línea de visión directa correr hacia él, o gritar e informar a otros enemigos dónde está el jugador ...
Falco
@Falco Dado que el juego ya no está basado en olas, estará basado en niveles y dado que los enemigos son zombis ... Estaba considerando hacerlo para que tengas que ser visto o hacer ruido para que te encuentren. Entonces, si usas un arma ruidosa? Emite un sonido en un rango y todos los enemigos en el camino de alcance hacia la ubicación del sonido emitido, y luego se dirigirá al azar alrededor de esa área.
Darin Beaudreau

Respuestas:

49

Esto suena como un caso de uso para los campos de flujo.

En esta técnica, realiza una única consulta de búsqueda de ruta hacia afuera desde su (s) objeto (s) de jugador, marcando cada celda que encuentra con la celda desde la que llegó.

Si todas sus fichas / bordes tienen el mismo costo transversal, entonces puede usar una búsqueda simple de amplitud para esto. De lo contrario, el algoritmo de Dijkstra (como A * sin objetivo / heurístico) funciona.

Esto crea un campo de flujo: una tabla de búsqueda que asocia cada celda con el siguiente paso hacia el objeto jugador más cercano desde esa posición.

Ahora tus enemigos pueden buscar su posición actual en el campo de flujo para encontrar el siguiente paso en su camino más corto para evitar obstáculos al objeto jugador más cercano, sin que cada uno haga su propia consulta de búsqueda de camino.

Esto escala mejor y mejor cuantos más enemigos tengas en tu rebaño. Para un solo enemigo, es más costoso que A * porque busca en todo el mapa (aunque puede salir temprano una vez que haya alcanzado a todos los agentes de búsqueda de caminos). Pero a medida que agrega más enemigos, pueden compartir cada vez más el costo de encontrar rutas al calcular segmentos de ruta compartidos una vez en lugar de una y otra vez. También obtiene una ventaja del hecho de que los BFS / Dijkdtra son más simples que A *, y generalmente son más baratos de evaluar por celda inspeccionada.

Exactamente dónde llega el punto de equilibrio, desde A * individual que es más barato, hasta A * con la memorización más barata (donde reutiliza algunos de los resultados para una consulta de búsqueda de ruta anterior para acelerar el siguiente), para que los campos fluyan más barato, dependerá de su implementación, la cantidad de agentes y el tamaño de su mapa. Pero si alguna vez planea un gran enjambre de enemigos que se acercan desde múltiples direcciones en un área confinada, un campo de flujo seguramente será más barato que A * iterado.

Como un ejemplo extremo, puede ver un video aquí con 20 000 agentes, todos simultáneamente buscando rutas en una cuadrícula razonablemente pequeña .

DMGregory
fuente
Esta técnica suena muy bien. Lo comprobaré.
Darin Beaudreau
15
Es posible usar un algoritmo híbrido que construye un campo de flujo parcial sin buscar más en el mapa de lo que lo harían las llamadas repetidas a A *, y nunca buscar la misma posición dos veces. La idea básica es elegir un enemigo arbitrario y comenzar una búsqueda A * del jugador hacia ese enemigo, marcando las celdas a medida que las encuentres como en la generación de campo de flujo normal. Una vez que la búsqueda encuentre a ese enemigo, elija otro enemigo (que aún no haya encontrado) como objetivo, vuelva a ordenar el conjunto abierto de acuerdo con la nueva heurística y continúe buscando. Detente cuando hayas encontrado a todos los enemigos.
Ilmari Karonen
1
¿Qué hay de evitar colisiones? Eso (algo) se menciona en el OP (evitando el recorte cuando llegan al jugador). Me parece que tendrías que volver a ejecutar los djikstras completos cada vez que algo se moviera (o agregar alguna lógica adicional)
Marte
2
@Mars El OP habla sobre la congregación, por lo que supongo que todas las personas pueden moverse a la misma velocidad; Los únicos lugares donde las colisiones serán un problema son los cuellos de botella, que requieren que parte del rebaño se detenga y espere. Sin embargo, en realidad no es necesario cambiar la búsqueda de ruta: una cola simple probablemente funcionaría lo suficientemente bien en la mayoría de los casos, y algunos sesgos de ruta (alguna selección pseudoaleatoria de rutas alternativas con costos similares) funcionarán para producir una bandada de aspecto más natural flujos que también evitan que toda la parvada intente atravesar una brecha particular de un solo mosaico :)
Luaan
3
@Luaan En un juego basado en fichas, te sorprenderías con qué frecuencia ocurren las colisiones. Personalmente, considero que la opción "hacer cola" es menos que óptima. Además, si las unidades no pueden cruzarse entre sí, deberá volver a calcular cuando las unidades comiencen a llegar a su posición final y a otros casos extremos. El flocado es difícil;)
Marte
8

A * no es pesado rendimiento. Me acercaría a esta situación variando los algoritmos. Haga A * de vez en cuando y, en el medio, verifique si el siguiente paso es libre o si necesita evasión.

Por ejemplo, rastrea la distancia de los jugadores desde la ubicación del objetivo A *, si está por encima de un umbral, recalcula a * y luego simplemente actualiza los movimientos. La mayoría de los juegos usan una combinación de puntos intermedios, por ejemplo, una cuadrícula simplificada para encontrar el camino y una lógica que maneja el movimiento entre puntos intermedios con algoritmos de dirección de evasión que usan rayos. Los agentes intentan correr a un punto de referencia distante maniobrando alrededor de obstáculos en su proximidad, es el mejor enfoque en mi opinión.

Aquí es mejor trabajar con máquinas de estados finitos y leer el libro "Programando la IA del juego por ejemplo" de Mat Buckland. El libro ofrece técnicas comprobadas para su problema y detalla las matemáticas requeridas. El código fuente del libro está disponible en la web; el libro está en C ++ pero algunas traducciones (incluida Java) están disponibles.

D3d_dev
fuente
2
Con un enfoque A * que se actualiza con poca frecuencia, puede ser útil escalonar sus actualizaciones, manteniendo un presupuesto para la cantidad de enemigos que pueden reencaminar en un solo cuadro. De esa manera, puede mantener su costo máximo de búsqueda de ruta por cuadro limitado, y manejar de manera más robusta muchas rutas de IA al amortizar su costo total en varios cuadros. Una IA que utiliza una ruta obsoleta para un cuadro o dos cuando se ha excedido el presupuesto para el cuadro, o que recurre a un ajuste de cuentas muerto si está cerca, por lo general no será perjudicial.
DMGregory
2
Probablemente establezca lo obvio aquí, pero si solo va a actualizar algunas de sus rutas en un marco dado, es posible que desee un sistema de prioridad basado en la distancia al jugador. Es probable que sea más importante que los enemigos cercanos al jugador actualicen sus caminos, mientras que probablemente esté bien que los enemigos lejanos usen un camino rancio.
AC
4

No solo es factible, creo que se hizo en un juego comercial en los años 90: BattleZone (1998).

Ese juego tenía unidades 3D con movimiento libre no basado en fichas y construcción de base basada en fichas.

Así es como parecía funcionar:

Primero, A * o algo similar (probablemente una variación de A * con límites estrictos sobre cuánto tiempo puede encontrar una ruta, por lo que nunca se necesitan demasiados recursos para ejecutarse, pero no siempre encuentra una ruta hasta el destino) se usaría para encontrar un camino para que un hovertank llegue a su destino sin atascarse en obstáculos basados ​​en fichas.

Luego, el tanque volaría alrededor del espacio sin mosaico como si fuera atraído hacia el centro de una baldosa cercana en su camino, y rechazado por obstáculos, otros tanques cercanos, etc.

Robyn
fuente
1
Entonces, ¿cuál es una buena manera de manejar siguiendo el camino, pero no exactamente? Si permito las curvas de los niños, necesito poder evitar que los enemigos choquen con la esquina de un obstáculo. ¿Debo mantener el comportamiento de flocado tanto para los enemigos como para los obstáculos y agregar A * para lidiar con esas situaciones?
Darin Beaudreau