Geração Procedural de uma Dungeon: Um jogo estilo Roguelike

6

Tagged

Attached Files

The following files have been attached to this tutorial:

.capx

Stats

3,345 visits, 4,410 views

Tools

License

This tutorial is licensed under CC BY 4.0. Please refer to the license text if you wish to reuse, share or remix the content contained within this tutorial.

Published on 13 Feb, 2017. Last updated 25 Feb, 2019

A tradução deste artigo se deu por uma pesquisa que venho fazendo sobre geração procedural. Em breve um artigo estendido será publicado e colocarei o link aqui.

Para mais informações, acesse Cup Pixel, você pode falar comigo em @luizfreinoso ou @cuppixel.

Visão Geral

Neste tutorial, aprenderemos como criar um jogo estilo Roguelike. Este estilo envolve a "geração procedural", o que significa que o design da fase (salas, corredores, posicionamento jogador/item/inimigo) é aleatoriamente determinado cada vez que o jogo inicia, mas o layout é gerado de forma inteligente (em particular, cada sala pode ser acessada por um corredor que leva a ela).

Aqui vamos implementar o algoritmo apresentado em: http://gamedevelopment.tutsplus.com/tutorials/create-a-procedurally-generated-dungeon-cave-system--gamedev-10099 usando o Construct 2. Os principais passos neste processo são:

0. Preencher a fase com pequenos quadrados com o comportamento Sólido (behaviour Solid).

1. Criar um total de "N quartos" com tamanhos aleatórios em locais aleatórios.

2. Criar um corredor horizontal e um corredor vertical entre pares de salas, ligando o quarto 1 ao quarto 2, o quarto 2 ao quarto 3, ..., o quarto N ao quarto 1 (escolhemos ligar o último quarto para o primeiro, para que não haja "beco sem saída" entre os quartos em nosso nível).

3. "Desenterrar as salas". Em outras palavras, remover todos os objetos que sobrepõem os sprites de salas/corredores.

4. (Opcional, mas recomendada para eficiência): Remover qualquer um dos blocos restantes que não limitem a sala/corredor.

5. Colocar o jogador em uma sala, colocar um item em cada sala e colocar inimigos em algumas entradas.

Colocação de Pedra e Sala

Primeiro, neste exemplo, montamos o projeto com um tamanho de layout de 1600x1600 (quando terminarmos, o tamanho da janela será de 400x400, mas para fins de teste, usamos um tamanho de janela de 1600x1600, para coincidir com o tamanho do layout, para visualizarmos todo o nível). Utilizamos loops aninhados para gerar uma grade de objetos denominados "Stone" (Pedra), que são 32x32 e têm o comportamento "Sólido". Também criamos um objeto TiledBackground chamado "StoneBorder", usando a mesma imagem do objeto Stone e tendo também o comportamento "Sólido", e criamos quatro versões 1600x32 deste objeto e posicionamos/rotamos para usar como paredes de contorno para nossa fase.

Em seguida, criamos um certo número de salas, definindo aleatoriamente seu tamanho e posição conforme mostrado na folha de eventos abaixo. É possível que alguns quartos se sobreponham, mas tudo bem - o resultado final será interessante - salas moldadas e com mais de um item.

Para cada Room (Salas), geramos um objeto associado chamado RoomCenter, que será útil (para fins de filtragem) quando precisarmos criar os Halls (Corredores). Também definimos uma variável de instância chamada "ID" para Rooms e RoomCenters, os objetos Halls que criamos conectarão uma Sala com ID = X a um RoomCenter com ID = X + 1. Para que a Sala final se conecte à Sala 1, usamos um pequeno truque: mudamos a ID do RoomCenter 1 para ser '1 a mais' (N + 1) do que o número total de quartos.

Neste ponto, uma possível fase que poderia ser gerada é semelhante a esta:

Colocação do corredor

Em seguida, ligamos a Sala com ID = X à Sala (Centro) com ID = X + 1 usando dois corredores: um horizontal e um vertical.

Os corredores horizontais precisam ter largura igual à distância horizontal que separa as salas, calculada usando abs (Room.X - RoomCenter.X) e a altura pode ser qualquer constante, um pouco maior que as dimensões do sprite jogador/inimigo. Estes corredores serão colocados para se alinhar verticalmente com a sala (ajuste Hall.Y para ser igual a Room.Y), como a âncora do sprite está no centro por padrão, a coordenada X precisa ser o ponto médio entre as duas salas, calculado usando (Room.X + RoomCenter.X) / 2.

Corredores verticais são dimensionados/colocados de forma semelhante, exceto que os cálculos horizontal/vertical sejam alternados. A largura é constante e a altura é abs (Room.Y - RoomCenter.Y); a coordenada X deve ser igual a RoomCenter.Y e a coordenada Y deve ser igual a (Room.Y + RoomCenter.Y) / 2.

OBS: a função abs nos retorna um valor absoluto, ex: abs(-5) = 5.

Neste ponto, uma possível fase que pode ser gerada é semelhante a esta:

Colocação de Fronteiras e Remoção de Pedras

Neste ponto, poderíamos destruir todas as pedras que se sobrepõem a uma Sala (Room) ou a um Corredor (Hall), e teríamos uma fase funcional. No entanto, isso deixa muitos sprites de Pedra (Stone) extras que exigem tempo de renderização adicional, e que nunca podem ser colididos, então eles não influenciam a jogabilidade. Portanto, damos o passo extra de criar objetos Border (Fronteira): defina cada Sala e cada Corredor para gerar um objeto "Border" (Fronteira) cujo tamanho é definido em uma quantidade fixa maior do que a Sala/Corredor que o gera.

Então, ao remover objetos Stone (Pedra), queremos destruir qualquer objeto que se sobrepõe a uma Sala (Room), Corredor (Hall) ou não está sobrepondo nenhum dos objetos de Fronteira (Border). Esta última condição é um pouco complicada de implementar. É tentador criar o evento:

Condição: Pedra X (invertida) é sobreposição de borda --- Ação: destruir pedra

No entanto, é quase garantido que cada pedra pode encontrar uma borda que não é sobreposição, assim, a condição será verdadeira para todas as pedras e todos elas serão destruídas. Então, em vez disso, criamos dois eventos: damos as Stones (Pedras) uma variável de instância booleana chamada 'StoneBorder', inicializada como 'false'. Após a criação das bordas, se uma pedra estiver sobrepondo uma borda, definimos o valor de 'StoneBorder' como 'true'. Finalmente, destruímos todas as Pedras cuja variável 'StoneBorder' está definida como 'false'.

Neste ponto, uma possível fase que poderia ser gerada (antes da remoção das pedras) se parece com isto:

Colocação do Jogador/Item/Inimigo

Neste ponto, você pode colocar um item no centro de cada Sala (Room) para o jogador coletar, colocar o jogador na Sala 1 (deslocar um pouco do centro da sala) e escolher aleatoriamente alguns Corredores (Halls) para colocar os inimigos ( colocando inimigos em Corredores ao invés de Salas reduz a chance de que eles estarão muito perto do lugar no início do jogo). Para tornar os inimigos interativos, você poderia adicionar o comportamento da "Linha de Visão" (Line Of Sight), e mandá-los perseguir o Jogador (Player) quando o ele está à vista (eu costumo alterar o alcance deste comportamento para metade do tamanho da janela, então eles só começam a persegui-lo quando você pode vê-los).

Toques finais

Neste ponto, você irá certificar-se de que seu tamanho de janela é menor do que seu tamanho disponivel (desde que o jogo não seja tão diverto se você puder ver tudo de uma vez). Além disso, você deve tornar invisível todos os seus objetos Room / RoomCenter / Hall / Border, eles têm servido a sua finalidade. Também é uma boa idéia incluir uma camada GUI (com o Parallax definido como 0,0) contendo um objeto Text que informa quantos objetos você coletou e quantos permanecem. Esses recursos foram implementados no arquivo Capx anexado a este tutorial. Outras adições divertidas a considerar incluem dar ao jogador uma arma para balançar ou atirar em inimigos, ou salas que contêm armadilhas que você pode levar inimigos para destruí-los. Muitas possibilidades existem, sua imaginação é o limite!

.CAPX
  • 0 Comments

Want to leave a comment? Login or Register an account!