O que você está procurando?
Engine & platform

Padrões procedurais que você pode usar com Tilemaps, parte 1

ETHAN BRUINS / UNITY TECHNOLOGIES Contributor
May 29, 2018|10 Min
Padrões procedurais que você pode usar com Tilemaps, parte 1
Esta página da Web foi automaticamente traduzida para sua conveniência. Não podemos garantir a precisão ou a confiabilidade do conteúdo traduzido. Se tiver dúvidas sobre a precisão do conteúdo traduzido, consulte a versão oficial em inglês da página da Web.

Muitos criadores usaram geração procedural para adicionar alguma diversidade ao seu jogo. Algumas menções notáveis incluem nomes como Minecraftou, mais recentemente, Enter the Gungeon e Descenders. Esta postagem explica alguns dos algoritmos que você pode usar com o Tilemap, introduzido como um recurso 2D no Unity 2017.2, e o RuleTile.

Com mapas criados proceduralmente, você pode ter certeza de que nenhuma jogada do seu jogo será igual à outra. Você pode usar várias entradas, como o tempo ou o nível atual do jogador, para garantir que o conteúdo mude dinamicamente, mesmo depois que o jogo for criado.

Sobre o que é esta postagem do blog?

Vamos dar uma olhada em alguns dos métodos mais comuns de criação de um mundo procedural e algumas variações personalizadas que criei. Aqui está um exemplo do que você pode criar depois de ler este artigo. Três algoritmos estão trabalhando juntos para criar um mapa, usando um Tilemap e um RuleTile:

Mundo processual - introdução
Mundo processual - introdução

Quando geramos um mapa com qualquer um dos algoritmos, receberemos uma matriz int que contém todos os novos dados. Podemos então pegar esses dados e continuar a modificá-los ou renderizá-los em um tilemap.

É bom saber antes de continuar lendo:

A maneira de distinguir o que é um bloco e o que não é é usando binário. 1 sendo ligado e 0 sendo desligado.

Armazenaremos todos os nossos mapas em uma matriz de inteiros 2D, que será retornada ao usuário no final de cada função (exceto quando renderizamos).

Usarei a função de matriz GetUpperBound() para obter a altura e a largura de cada mapa, para que tenhamos menos variáveis em cada função e um código mais limpo.

Costumo usar Mathf.FloorToInt()porque o sistema de coordenadas do Tilemap começa no canto inferior esquerdo e usar Mathf.FloorToInt() nos permite arredondar os números para um inteiro.

Todo o código fornecido nesta postagem do blog está em C#.

Gerar matriz

GenerateArray cria um novo array int do tamanho fornecido a ele. Também podemos dizer se o array deve estar cheio ou vazio (1 ou 0). Aqui está o código:

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

Mapa de renderização

Esta função é usada para renderizar nosso mapa no tilemap. Percorremos a largura e a altura do mapa, colocando peças somente se a matriz tiver um 1 no local que estamos verificando.

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

Atualizar Mapa

Esta função é usada apenas para atualizar o mapa, em vez de renderizá-lo novamente. Dessa forma, podemos usar menos recursos, pois não precisamos redesenhar cada bloco e seus dados.

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

Ruído Perlin

O ruído Perlin pode ser usado de várias maneiras. A primeira maneira de usá-lo é criar uma camada superior para nosso mapa. Isso é tão simples quanto obter um novo ponto usando nossa posição x atual e uma semente.

Simples

Esta geração adota a forma mais simples de implementar o Ruído Perlin na geração de níveis. Podemos usar a função Unity para Perlin Noise para nos ajudar, então não há necessidade de nenhuma programação complexa. Também garantiremos que temos números inteiros para nosso tilemap usando a função Mathf.FloorToInt().

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

É assim que ele fica renderizado em um tilemap:

Geração de mapas
Geração de mapas
Suavizado

Também podemos pegar essa função e suavizá-la. Defina intervalos para registrar a altura de Perlin e, em seguida, suavize entre os pontos. Essa função acaba sendo um pouco mais avançada, pois temos que levar em conta Listas de inteiros para nossos intervalos.

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

Para a primeira parte desta função, primeiro verificamos se o intervalo é maior que um. Se for, então geramos o ruído. Fazemos isso em intervalos para permitir a suavização. A próxima parte é trabalhar para suavizar os pontos.

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

O alisamento acontece através das seguintes etapas:

- Obtenha a posição atual e a última posição

- Obtenha a diferença entre as duas posições, a informação chave que queremos é a diferença no eixo y

- Em seguida, determinamos o quanto devemos alterar o acerto. Isso é feito dividindo a diferença y pela variável de intervalo.

Agora podemos começar a definir as posições. Nós trabalharemos para chegar a zero

Quando atingirmos 0 no eixo y, adicionaremos a alteração de altura à altura atual e repetiremos o processo para a próxima posição x

Depois de termos feito todas as posições entre a última posição e a posição atual, passaremos para o próximo ponto

Se o intervalo for menor que um, simplesmente usamos a função anterior para fazer o trabalho para nós.

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

Vamos ver como fica renderizado:

Alisamento
Alisamento
Caminhada aleatória
Caminhada aleatória Topo

O funcionamento desse algoritmo é através do lançamento de uma moeda. Então obtemos um de dois resultados. Se o resultado for cara, subimos um bloco; se o resultado for coroa, descemos um bloco. Isso cria alguma altura em nosso nível, pois ele sempre se move para cima ou para baixo. A única desvantagem desse algoritmo é que ele parece muito quadrado. Vamos dar uma olhada em como isso funciona.

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

Esta geração nos dá uma altura mais suave quando comparada à geração de ruído Perlin.

Caminhada aleatória superior suavizada

Esta geração nos dá uma altura mais suave quando comparada à geração de ruído Perlin.

Esta variação do Random Walk permite um acabamento muito mais suave do que a versão anterior. Podemos fazer isso adicionando duas novas variáveis à nossa função:

  • A primeira variável é usada para determinar por quanto tempo mantivemos nossa altura atual. Este é um número inteiro e é redefinido quando alteramos a altura.
  • A segunda variável é uma entrada para a função e é usada como nossa largura mínima de seção para a altura. Isso fará mais sentido quando você tiver visto a função

Agora sabemos o que precisamos adicionar. Vamos dar uma olhada na função:

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

Como você pode ver no gif abaixo, a suavização do algoritmo de caminhada aleatória permite algumas partes planas e agradáveis dentro do nível.

Jogando uma moeda
Conclusão

Espero que isso tenha inspirado você a começar a usar alguma forma de geração procedural em seus projetos. Se você quiser aprender mais sobre mapas de geração procedural, confira o Procedural Generation Wiki ou o Roguebasin.com, que são ótimos recursos.

Você pode aguardar a próxima postagem da série para ver como podemos usar geração procedural para criar sistemas de cavernas.

Se você fizer algo legal usando geração procedural, fique à vontade para me enviar uma mensagem no Twitter ou deixar um comentário abaixo!

Geração de procedimentos 2D na Unite Berlin

Quer saber mais sobre isso e assistir a uma demonstração ao vivo? Também falarei sobre Padrões Procedurais para usar com Tilemaps no Unite Berlin, no mini teatro do salão de exposições em 20 de junho. Estarei por perto depois da palestra se você quiser bater um papo pessoalmente!