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

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.

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#.
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`
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`
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`
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.
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:

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:

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.
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.

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!
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!