Infinite 3D Cave in Unity

8

Un amigo y yo esperamos hacer un juego en Unity en el que vueles a través de una cueva 3D infinita que pueda girar y enrollarse en cualquier dirección (aunque obviamente no hasta el punto de que los giros son imposibles de hacer). Estábamos pensando en crear una serie de "piezas" de túnel que cada curva una cierta cantidad, y engendrar cada uno al final de la anterior.

Pero no tenemos idea de cómo asegurarnos de que la boca de una pieza del túnel siempre se alinee perfectamente (tanto en posición como en rotación) con el final de la anterior. ¿Alguien puede ofrecer algún consejo sobre cómo lograr esto?

¿Incluso lo estamos haciendo de la manera correcta, o hay una mejor manera de generar la cueva procesalmente? Puntos de bonificación: Sería increíble si la cueva también pudiera cambiar de diámetro y / o forma, aunque eso sería solo salsa.

richardmherndon
fuente

Respuestas:

6

Raramente hay un "camino correcto" o "camino equivocado" cuando se trata del diseño del juego. Hay muchas, muchas maneras de resolver este problema, pero aquí hay algunos enfoques posibles para explorar:

  • Restrinja las piezas del túnel para que comiencen y terminen solo en ciertas direcciones; por ejemplo solo a lo largo de los ejes. Luego solo tiene que hacer un seguimiento del desplazamiento desde el principio hasta el final de un segmento, junto con enumeraciones que describen la dirección del movimiento al inicio y al final del segmento. De esta manera, no tiene que preocuparse por rotar las mallas del túnel, siempre que elija la siguiente de manera que comience en la misma dirección en la que terminó la última.

  • Haga que cada segmento comience en el origen de su espacio de modelo local con el túnel viajando a lo largo de un eje específico (+ X, + Z o -Z serían las opciones más lógicas, pero todos los modelos deberían usar el mismo), luego almacene la posición del final del túnel y la dirección final de desplazamiento de alguna manera para que la siguiente malla se pueda transformar correctamente. (una matriz de transformación es probablemente la forma más fácil de almacenar esta información, pero también podría usar un vector de desplazamiento + cuaternión, cuaternión dual, desplazamiento + nuevos vectores base, desplazamiento + rotaciones de ángulo de Euler, etc.)

  • Genere procesalmente su cueva al transmitir nuevos datos de vértice a algunas mallas. Puedes hacer esto usando la Meshclase . Al generar nuevos datos de vértice, la forma más fácil es probablemente elegir un punto en algún lugar aproximadamente en la misma dirección que el segmento anterior de la cueva, luego dejar que el centro de la cueva se mueva hacia ese punto. Luego puede usar coordenadas cilíndricas para crear detalles de procedimiento en las paredes de la cueva. Piense en ello como extrudiendo el extremo de un cilindro, luego traduciendo individualmente cada vértice más cerca o más lejos del centro de ese cilindro.

Cualquier solución que use segmentos prefabricados requerirá que se asegure de que todas las mallas tengan la misma forma y diámetro alrededor del centro del túnel, pero puede evitar esto de alguna manera haciendo que los segmentos se superpongan en cierta medida y que cada segmento se ensanche. en los extremos Si se hace bien, no debería ser demasiado obvio para el jugador que hay una costura.

Por otro lado, si opta por una geometría generada completamente por procedimientos, tendrá más trabajo para asegurarse de no generar secciones que sean imposibles de atravesar, y podría tener problemas con la detección de colisiones.

Tenga en cuenta que, con cualquier juego "infinito", debe tener en cuenta las limitaciones de las representaciones de coma flotante. Si el jugador se aleja demasiado del origen mundial, se vuelve fácil perder precisión en los cálculos de coma flotante (cuando dos valores grandes se restan entre sí, por ejemplo). Para evitar esto, puede hacer que el mundo se mueva alrededor del jugador, en lugar de que el jugador se mueva por el mundo, pero generalmente es más fácil verificar la posición del jugador de vez en cuando, y si están demasiado lejos del origen, reconstruya el mundo con el jugador en o cerca del origen.

bcrist
fuente
2
+1 especialmente para el comentario de 'no hay manera correcta' (aunque tendré que estar un poco en desacuerdo: hay muchas, muchas formas incorrectas ...)
Steven Stadnicki
¡Muchas gracias! Terminamos usando algunas piezas de túnel diferentes con ubicaciones y direcciones finales conocidas, estableciendo marcadores en esas ubicaciones / ángulos, y colocando cada nueva pieza en relación con el marcador de la pieza anterior. Hubiera sido genial hacer algo más elaborado, pero por el momento, la generación de procedimientos más legítimos estaba fuera de nuestro rango de habilidades y límite de tiempo. ¡Gracias de nuevo!
richardmherndon
3

Aquí hay una técnica con la que experimenté recientemente. Mi prototipo RenderMonkey muestra una sección del cañón de estilo badlands, pero el mismo principio debería funcionar en las cuevas.

La idea es comenzar con mosaicos que sean genéricos, francamente aburridos, con bordes simples predecibles para que sean fáciles de alinear sin costuras ni espacios:

Azulejo opaco y predecible

Estas fichas iniciales pueden ser formas que haya modelado o tubos de macarrones generados por procedimientos de geometría cilíndrica (esta forma es una variante de las sugerencias de bcrist y Steven Stadnicki). El uso de modelos que ha creado facilita el manejo de topologías arbitrarias, como rutas de ramificación, o puntos de interés como cavernas abiertas. Esto todavía es posible con procedimientos puros (ver la sugerencia de Gyroninja sobre las técnicas de metaball), pero desafiante.

Una vez que un mosaico se coloca en el mundo, desplaza sus vértices usando las funciones de ruido aplicadas en el espacio mundial. Esto preserva la conectividad y la fluidez entre los mosaicos (dado que los vértices coincidentes tienen la misma entrada del espacio mundial y obtienen la misma salida de desplazamiento), pero hace que cada mosaico se vea único y orgánico:

Eso es mas interesante

La textura y las normales también se aplican en el espacio mundial, aquí utilizando el mapeo triplanar, de modo que los mosaicos adyacentes sean completamente perfectos, sin las difíciles restricciones de desenvolvimiento UV.

También más interesante.

La esperanza es que una técnica como esta le brinde la facilidad de planificación y control de diseño de nivel de un mapa en mosaico, sin repetición visible o estructura de aspecto mecánico en el resultado jugable.

Puede usar una malla de baja resolución con solo los componentes de ruido de baja frecuencia aplicados para crear la representación de colisión. Como señala bcrist, deberá controlar la amplitud máxima del ruido en relación con el radio y la nitidez de las curvas del túnel, para asegurarse de que nunca se pellizque por completo.

Una nota más: si tu cueva es realmente infinita, es posible que necesites "volver a centrarla" periódicamente a medida que el jugador se mueva más y más desde el origen. Debido a que los números de coma flotante pierden precisión a grandes magnitudes, la física y los artefactos de renderizado pueden arrastrarse a distancias extremas. Si hace esto, querrá que el ruido de su espacio mundial sea periódico a gran escala, con el período exactamente coincidente con su desplazamiento de centrado, para que no encuentre costuras después de centrarse.

DMGregory
fuente
2

Puede modelar su cueva como una secuencia de puntos, cada uno con un tamaño asociado, con líneas que los conectan. Luego trate cada punto y línea como metabolas y metacilindros. Esto le da una forma básica para su cueva, a la que es posible que desee comenzar a agregar variación, como al compensar vértices al azar.

Gyroninja
fuente
2

Aquí hay otro enfoque para la generación de procedimientos que aún no se ha mencionado explícitamente: el pelado de spline. Puedes usar una versión de Hermite Splines(que proporcionan una curva que interpola posiciones y tangentes) para definir las curvas: cuando llegue el momento de generar un nuevo segmento, simplemente elija una posición (aproximadamente en la dirección del segmento anterior, como dice bcrist) y una dirección (aproximadamente en el mismo dirección: por ejemplo, dentro de un cono bien definido de la dirección anterior), luego use la nueva posición + dirección y su posición + dirección anterior para construir una nueva 'columna vertebral' para su cueva. Una vez que tenga esta columna vertebral, puede pelarla con una construcción cilíndrica: determine las posiciones y tangentes de (por ejemplo) 10 puntos a lo largo de la curva, use esas posiciones / tangentes para encontrar un 'marco' ortogonal y luego use estos marcos para construir segmentos cilíndricos. Una pequeña precaución con esto es que la cueva no puede curvarse demasiado mucho, o de lo contrario puede tener problemas de auto-intersección.

EDITAR: Aquí hay un desglose aproximado del pseudocódigo del algoritmo:

Parameters:
  L = (average) segment length,
  V = segment length variation,
  R = cylinder radius,
  T = segment angular variation
  S = number of 'rings' per segment

Setup:
Choose an initial point P_0 and direction D_0 (for concreteness' sake, these can be
the origin and the X axis).  Set P_prev and D_prev to these values.
Initialize u_prev to be the Y axis and v_prev to be the Y and Z axes.
  (Note that (D_prev, u_prev, v_prev) form a mutually-orthogonal 'coordinate frame')

Generate a segment (do this as many times as you want):
{
  Choose a (temporary) direction D within a cone of size T around the previous direction D_prev
  Choose a segment length L_cur = at random from within the range [L-V, L+V].
  Set the current terminal point P_cur to P_prev+D*L_cur - this is the position
  we'll interpolate to
  Set the current terminal direction D_cur to a direction chosen at random from
  within a cone of size T around the previous direction.  (There are good ways
  of doing this - if you look back through gamedev.SE you should find some)
  'Build' the Hermite spline H that goes from (P_prev, D_prev) to (P_cur, D_cur)

  Now, skin that spline:
  for ( i = 1; i <= S; i++ ) {
    find the position P of the hermite spline H at t=i/S
    find the direction D of the spline at t (this will be just the derivative)
    'transport' the orthogonal frame to the new spot: for instance,
      v_new = D x u_prev
      u_new = v_new x D
    (note that this keeps u_new, v_new close to their old values, and orthogonal
    to each other and to D)
    Use the previous and current frames and positions to build a cylindrical 'ring':
    For theta from 0 to 2pi {
      find the points (P+(u_new, v_new, D) * (cos theta, sin theta, 0))
      and connect them to their counterparts from the previous ring
      (note that that multiplication is a matrix-vector multiply)
    }
    update u_prev and v_prev to u_new and v_new
  }
  update the other prev variables to their 'new' values
}

Esto es obviamente un pseudocódigo muy aproximado; si hay algo que no está claro, házmelo saber y trataré de explicarlo, pero será difícil cubrir todos los detalles sin un gran volcado de código ...

Steven Stadnicki
fuente
(Por cierto, si desea un pseudocódigo para este enfoque, hágamelo saber; tuve que hacer algo así en un trabajo anterior, así que
terminé resolviendo
Me gustaría ver tu implementación; También hice algo similar una vez, pero en su lugar utilicé curvas 3D de Bezier cúbicas.
bcrist