¿Cómo calcular rápidamente el área de visión en un juego en mosaico 2D?

24

Tengo una matriz de mosaicos, en algunos de esos mosaicos hay objetos. Quiero calcular qué mosaicos son visibles para el jugador y cuáles no, y necesito hacerlo de manera bastante eficiente (para que calcule lo suficientemente rápido incluso cuando tengo matrices grandes (100x100) y muchos objetos).

Traté de hacerlo con el algoritmo de línea de Bresenham , pero fue lento. Además, me dio algunos errores:

----XXX-        ----X**-     ----XXX-
-@------        -@------     -@------
----XXX-        ----X**-     ----XXX-
(raw version)   (Besenham)   (correct, since tunnel walls are 
                              still visible at distance)

(@ is the player, X is obstacle, * is invisible, - is visible)

Estoy seguro de que esto se puede hacer, después de todo, tenemos NetHack, Zangband, y todos trataron este problema de alguna manera :)

¿Qué algoritmo puedes recomendar para esto?


Para mis necesidades, definiré visible de esta manera: el mosaico es visible cuando al menos una parte (por ejemplo, una esquina) del mosaico se puede conectar al centro del mosaico del jugador con una línea recta que no se cruza con ninguno de los obstáculos.

Rogach
fuente
1
Vaya, mi error, NetHack no estaba jugando con la línea de visión :)
Rogach
Algunas ideas antiguas se pueden encontrar en fadden.com/tech/fast-los.html , aunque eso se remonta a los días en que las CPU eran bastante lentas y era mejor evitar los cálculos de coma flotante.
fadden

Respuestas:

10

Su definición de visible es la siguiente:

el mosaico es visible cuando al menos una parte (por ejemplo, una esquina) del mosaico se puede conectar al centro del mosaico del jugador con una línea recta que no se cruza con ninguno de los obstáculos

Puedes implementar este concepto literalmente trazando rayos desde la ficha de tu jugador e intersectándolos con tu escena. Se rompe de cada iteración una vez que el rayo golpea un obstáculo (o excede un cierto umbral de distancia) ya que solo le interesan las fichas que el jugador puede ver directamente. Romperé el proceso por ti:

  1. Especifique el nivel de precisión que le gustaría dar al algoritmo. Esta será la cantidad de rayos que trazará.
  2. Divida el círculo completo de 360 ​​grados por la precisión elegida para saber cuántos grados rotar entre cada rayo.
  3. Comenzando en 0 grados e incrementándose en la cantidad determinada en el paso 2, cree un rayo con el origen en el centro de la ficha del jugador y la dirección determinada por el ángulo actual.
  4. Para cada rayo, comenzando desde la ficha del jugador, camine a lo largo de la dirección del rayo hasta llegar a una casilla de obstáculo. Agregue ese mosaico a la lista de mosaicos visibles y continúe con el siguiente rayo. También es posible que desee agregar una distancia máxima para "darse por vencido" en caso de que no se encuentre una colisión.

Aquí hay una imagen que muestra 3 rayos de ejemplo. Los mosaicos de color más oscuro son el "resultado" de cada rayo, es decir, dónde ocurrió la colisión. Sin embargo, deberías repetir esto en todo el círculo:

ingrese la descripción de la imagen aquí

Ajuste la distancia máxima y el número de rayos para el rendimiento. Demasiado poco y perderás fichas, demasiado y tu rendimiento se verá afectado. Además, cuanto más lejos tengan que viajar los rayos, mayor será el "error" y mayor precisión necesitará.

Editar

Consulte el siguiente tutorial sobre emisión de rayos, en particular el Paso 3 y el Paso 4, para ayudarlo a implementar el bit de intersección del algoritmo:

http://www.permadi.com/tutorial/raycast/rayc7.html

David Gouveia
fuente
¿Debería simplemente "caminar" a lo largo de cada rayo a una distancia fija (por ejemplo, 0.3 puntos) o necesito ejecutar algo como el algoritmo de Besenham en cada rayo?
Rogach
Si avanzas solo por una distancia fija, tendrás problemas con las fichas perdidas. Consulte este tutorial sobre la emisión de rayos . También editaré ese recurso en mi respuesta. Básicamente, verifica las colisiones horizontales y verticales por separado.
David Gouveia
1
El algoritmo es bueno, pero requerirá una gran cantidad de rayos para trabajar correctamente con túneles largos de 1 mosaico.
HolyBlackCat
@HolyBlackCat: ese será el caso solo si envía rayos en ángulos pares en todas las direcciones. Pero puede evitar enviar la mayoría de esos rayos y solo arrojarlos a los extremos de la línea en su escena. Aquí hay una buena explicación: redblobgames.com/articles/visibility
Rogach
8

Prefiero lanzar rayos de sombra en lugar de rayos de línea de visión.

Digamos que esta es su área de visualización (el área potencialmente visible)

######################
#####.............####
###................###
##..................##
#....................#
#....................#
#..........@.........#
#....................#
#....................#
##..................##
###................###
#####.............####
######################

Los # bloques no son visibles mientras que. son visibles

Pongamos un obstáculo X:

######################
#####.............####
###................###
##.....X.....XXX....##
#......X.......X.....#
#...X.XX.............#
#...X......@.........#
#...X..........X.....#
#...XXXXXX...........#
##..................##
###....X...........###
#####.............####
######################

Tiene una lista de las X que se encuentran dentro del área de visualización y luego marca como ocultas cada mosaico que está detrás de cada uno de estos obstáculos: cuando un obstáculo se marca como oculto, lo elimina de la lista.

######################
#####.............####
###................###
##.....X.....XXX....##
#......X.......X.....#
#...X.XX.............#
#...X......@.........#
#...X..........X.....#
#...XXXXX*...........#
##......##..........##
###....*#..........###
#####.###.........####
######################

En el ejemplo anterior, puede ver la sombra proyectada en el extremo derecho de la pared inferior y cómo esta sombra elimina el obstáculo oculto de la lista del obstáculo que debe verificar (X debe verificar; * marcado).

Si ordena la lista usando alguna partición binaria para que primero se verifiquen el costo X, puede acelerar ligeramente su verificación.

Puede usar una especie de algoritmo de "Batallas Navales" para verificar el bloque de X a la vez (básicamente buscando una X adyacente que esté en una dirección que pueda ensanchar el cono de sombra)

[EDITAR]

Se necesitan dos rayos para proyectar correctamente una sombra y, dado que un mosaico es rectangular, se pueden hacer muchas suposiciones utilizando las simetrías disponibles.

Las coordenadas del rayo se pueden calcular utilizando un espacio simple que se divide alrededor del mosaico de obstáculos:

Ejemplo de partición espacial

Cada área rectangular constituye una elección sobre qué esquina del mosaico debe tomarse como borde de cono de sombra.

Este razonamiento se puede impulsar aún más para conectar múltiples mosaicos adyacentes y permitirles lanzar un solo cono más ancho de la siguiente manera.

El primer paso es asegurarse de que no haya obstáculos hacia la dirección del observador, en ese caso se considera el obstáculo más cercano:

elige el obstáculo más cercano

Si el mosaico amarillo es un obstáculo, ese mosaico se convierte en el nuevo mosaico rojo.

Ahora consideremos el borde del cono superior:

fichas candidatas

Las fichas azules son todas posibles candidatas para permitir que el cono de sombra se ensanche: si al menos una de ellas es un obstáculo, el rayo se puede mover usando el espacio que se divide alrededor de esa ficha como se vio anteriormente.

El mosaico verde es un candidato solo si el observador está por encima de la línea naranja que sigue:

cheque extendido

Lo mismo significa el otro rayo y las otras posiciones del observador sobre el obstáculo rojo.

La idea subyacente es cubrir la mayor cantidad de área posible para cada lanzamiento de cono y acortar lo más rápido posible la lista de obstáculos para verificar.

FxIII
fuente
Enfoque interesante y probablemente una mejor idea debido a su naturaleza sustractiva. Después de leer esto, probablemente también lo implemente de esta manera.
David Gouveia
Puedo prever problemas en situaciones como esta . Jugador en amarillo, obstáculos en azul y morado. El jugador debería poder ver el obstáculo púrpura (como muestra el rayo verde). Pero el rayo rojo de sombra que pasa a través del obstáculo azul rechaza el azulejo púrpura. Pero supongo que la versión de línea de visión tiene el potencial de tener problemas más grandes que este.
David Gouveia
Este problema proviene de la definición de "oculto": cuando un rayo se cruza con un mosaico (casi) nunca lo cubrirá por completo. El mismo problema se resuelve con alias cuando se procesan segmentos de línea. Personalmente, creo que un mosaico está oculto cuando la mayor parte está cubierto, uno puede definir que está oculto está completamente cubierto, puede encontrar si expone un lado que podría ensanchar el cono de sombra ... De todos modos, puede eliminar solo los bloques que están completamente cubiertos.
FxIII
@DavidGouveia: ¿qué problemas más grandes?
Rogach
@DavidGouveia: ya probé un enfoque con "conos" de sombra, y fue muy ineficiente. En cuanto a la precisión de los rayos de visibilidad, ~ 5500 rayos son suficientes para ver las 20 baldosas de la pared en cada dirección si está parado directamente cerca de ella, y como la distancia en la que solo es visible una sola baldosa es mucho más. Y en cuanto a perder algunas fichas a mayor distancia, bueno, no todos tienen una visión perfecta, ¿eh?
Rogach
8

El problema que está intentando resolver a veces se llama Campo de visión, FOV para abreviar. Como mencionó roguelikes como ejemplos, debe echar un vistazo a lo que dice la wiki de RogueBasin sobre el tema (incluso hay enlaces a implementaciones): http://www.roguebasin.com/index.php?title=Field_of_Vision

Hay bastantes algoritmos diferentes con diferentes pros y contras: una comparación muy útil también está disponible en RogueBasin: http://www.roguebasin.com/index.php?title=Comparative_study_of_field_of_view_algorithms_for_2D_grid_based_worlds

Tapio
fuente
Muy bueno y completo resumen!
Rogach
Ese sitio web es un gran recurso, gracias por compartir ese enlace. También contiene una descripción increíblemente comprensible de cómo funciona la
búsqueda de rutas
El enlace en respuesta ahora va a la página de inicio del sitio: roguebasin.com/index.php?title=Category:FOV parece ser una coincidencia razonable.
fadden