Caverna 3D infinita na unidade


8

Eu e um amigo esperamos fazer um jogo no Unity, no qual você voa através de uma caverna 3D infinita que pode girar e girar em qualquer direção (embora obviamente não seja ao ponto que as curvas sejam impossíveis de realizar). Estávamos pensando em criar um número de "peças" do túnel que cada curva uma certa quantidade, e gerando cada uma no final da que veio antes.

Mas não temos idéia de como garantir que a boca de uma peça do túnel esteja sempre alinhada perfeitamente (na posição e na rotação) com o final da anterior. Alguém pode oferecer algum conselho sobre como fazer isso?

Será que estamos fazendo isso da maneira certa ou existe uma maneira melhor de gerar processualmente a caverna? Pontos de bônus: Seria incrível se a caverna também pudesse mudar de diâmetro e / ou de forma, embora isso fosse apenas molho.

Respostas:


6

Raramente existe um "caminho certo" ou "caminho errado" quando se trata de design de jogos. Existem muitas, muitas maneiras de resolver esse problema, mas aqui estão algumas abordagens possíveis para explorar:

  • Restrinja as peças do túnel para iniciar e terminar apenas em determinadas direções; por exemplo, apenas ao longo dos eixos. Depois, basta acompanhar o deslocamento do início ao fim de um segmento, juntamente com as enumerações que descrevem a direção do movimento no início e no final do segmento. Dessa forma, você não precisa se preocupar em girar as malhas do seu túnel, desde que você sempre escolha a próxima para que comece na mesma direção em que a última terminou.

  • Faça com que cada segmento inicie na origem do espaço do modelo local, com o túnel viajando ao longo de um eixo específico (+ X, + Z ou -Z seriam as escolhas mais lógicas, mas todos os modelos devem usar o mesmo). armazene a posição do fim do túnel e a direção final da viagem de alguma maneira, para que a próxima malha possa ser transformada corretamente. (uma matriz de transformação é provavelmente a maneira mais fácil de armazenar essas informações, mas você também pode usar um vetor de deslocamento + quaternion, quaternion duplo, deslocamento + novos vetores de base, deslocamento + rotação do ângulo de euler etc.)

  • Gere processualmente sua caverna transmitindo novos dados de vértice para algumas malhas. Você pode fazer isso usando a Meshclasse . Ao gerar novos dados de vértice, a maneira mais fácil é provavelmente escolher um ponto em algum lugar aproximadamente na mesma direção que o segmento anterior da caverna e deixar o centro da caverna se mover em direção a esse ponto. Em seguida, você pode usar coordenadas cilíndricas para criar detalhes de procedimento nas paredes da caverna. Pense nisso como extrudar a extremidade de um cilindro, traduzindo individualmente cada vértice para mais perto ou mais longe do centro desse cilindro.

Qualquer solução que use segmentos pré-fabricados exigirá que você garanta que todas as malhas tenham a mesma forma e diâmetro ao redor do centro do túnel, mas você pode contornar isso de certa forma fazendo com que os segmentos se sobreponham até um certo ponto e que cada segmento se alargue nas extremidades. Se bem feito, não deve ser muito óbvio para o jogador que há uma costura.

Por outro lado, se você optar por uma geometria inteiramente gerada por procedimentos, terá mais trabalho para garantir que não gere seções impossíveis de atravessar e poderá encontrar problemas com a detecção de colisão.

Lembre-se de que, em qualquer jogo "infinito", você deve estar ciente das limitações das representações de ponto flutuante. Se o jogador se distancia muito da origem mundial, fica fácil perder precisão nos cálculos de ponto flutuante (quando dois valores grandes são subtraídos um do outro, por exemplo). Para evitar isso, você pode fazer com que o mundo se mova ao redor do jogador, em vez de o jogador se mover pelo mundo, mas geralmente é mais fácil verificar a posição do jogador de vez em quando e, se estiverem muito longe da origem, reconstrua o mundo com o jogador na origem ou próximo a ela.


2
+1 especialmente para o comentário 'nenhuma maneira direita' (embora eu tenho que discordar um pouco: há muitas, muitas maneiras erradas ...)
Steven Stadnicki

Muito obrigado! Acabamos usando algumas peças diferentes do túnel com locais e direções finais conhecidas, definindo marcadores nesses locais / ângulos e colocando cada peça nova em relação ao marcador da peça anterior. Teria sido legal fazer algo mais elaborado, mas, por enquanto, a geração processual mais legítima estava fora do nosso alcance e limite de tempo. Obrigado novamente!
Richardmherndon

3

Aqui está uma técnica que eu experimentei recentemente. Meu protótipo RenderMonkey mostra uma seção do desfiladeiro no estilo ermo, mas o mesmo princípio deve funcionar em cavernas.

A idéia é começar com peças genéricas, totalmente chatas, com arestas previsíveis simples, para facilitar a alinhamento sem costuras ou lacunas:

Ladrilho maçante e previsível

Esses blocos iniciais podem ser formas que você modelou ou tubos de macarrão gerados procedimentalmente de geometria cilíndrica (esse formato é uma variante das sugestões de bcrist e de Steven Stadnicki). O uso de modelos que você criou facilita o manuseio de topologias arbitrárias, como caminhos de ramificação, ou pontos de interesse, como cavernas abertas. Isso ainda é possível com procedimentos puros (veja a sugestão de Gyroninja sobre técnicas metabólicas), mas desafiadores.

Depois que um bloco é colocado no mundo, desloque seus vértices usando as funções de ruído aplicadas no espaço do mundo. Isso preserva a conectividade e a uniformidade entre os blocos (já que os vértices coincidentes têm a mesma entrada no espaço mundial e obtêm a mesma saída de deslocamento), mas fazem com que cada bloco pareça exclusivo e orgânico:

Isso é mais interessante

A textura e os normais também são aplicados no espaço mundial - aqui, usando o mapeamento triplanar - para que os blocos adjacentes fiquem completamente sem costura, sem restrições complicadas de desembrulhar os raios UV.

Também mais interessante

A esperança é que uma técnica como essa lhe proporcione a facilidade de planejamento e controle de nível de um mapa lado a lado, sem repetição visível ou estrutura com aparência mecânica no resultado reproduzível.

Você pode usar uma malha de menor resolução com apenas os componentes de ruído de baixa frequência aplicados para criar a representação de colisão. Como observa o bcrist, você precisará controlar a amplitude máxima do ruído em relação ao raio e à nitidez das curvas do túnel, para garantir que ele nunca se solte completamente.

Uma observação adicional: se sua caverna for realmente infinita, talvez seja necessário "atualizá-la" periodicamente, à medida que o jogador se afasta cada vez mais da origem. Como os números de ponto flutuante perdem precisão em grandes magnitudes, a física e os artefatos de renderização podem surgir a distâncias extremas. Se você fizer isso, desejará que o ruído do espaço no mundo seja periódico em larga escala, com o período exatamente igual ao seu deslocamento de recentes, para não encontrar costuras após os recentes.


2

Você pode modelar sua caverna como uma sequência de pontos, cada um com um tamanho associado, com linhas conectando-os. Em seguida, trate cada ponto e linha como metaballs e metacylinders. Isso fornece uma forma básica para sua caverna, na qual você pode começar a adicionar variações, como deslocando aleatoriamente vértices.


2

Aqui está outra abordagem à geração de procedimentos que ainda não foi explicitamente mencionada: skin de spline. Você pode usar uma versão do Hermite Splines(que fornecem uma curva interpolando posições e tangentes) para definir as curvas: quando chegar a hora de gerar um novo segmento, basta escolher uma posição (aproximadamente na direção do segmento anterior, como diz bcrist) e uma direção (aproximadamente na mesma direção - por exemplo, dentro de um cone bem definido da direção anterior), use a nova posição + direção e sua posição anterior + direção para construir uma nova 'espinha' para sua caverna. Depois de ter esse backbone, você pode revesti-lo com uma construção cilíndrica: determine as posições e tangentes (por exemplo) de 10 pontos ao longo da curva, use essas posições / tangentes para encontrar um 'quadro' ortogonal e, em seguida, use esses quadros para construir segmentos cilíndricos. Um pequeno cuidado com isso é que a caverna não pode se curvar demasiadomuito,

EDIT: Aqui está um detalhamento pseudocódigo do 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
}

Este é obviamente um pseudocódigo muito grosseiro; se houver algo que não esteja claro, deixe-me saber e tentarei explicar, mas será difícil cobrir todos os detalhes sem apenas um imenso despejo de código ...


(Aliás, se você quiser pseudocódigo para esta abordagem deixe-me saber, eu tinha que fazer algo muito parecido com isso em um trabalho anterior, então eu acabei trabalhando fora todos os pequenos detalhes.)
Steven Stadnicki

Eu ficaria curioso para ver sua implementação; Eu fiz algo parecido uma vez também, mas usando curvas cúbicas 3D de Bezier.
usar o seguinte comando
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.