Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Game Development
  2. Tile-Based Games
Gamedevelopment

Jogo "Sokoban" 2D baseado em tilesets com Unity

by
Difficulty:BeginnerLength:LongLanguages:

Portuguese (Português) translation by Jonathan Ramos (you can also view the original English article)

Final product image
What You'll Be Creating

Neste tutorial iremos abordar a criação de um sokoban ou empurra-caixas usando uma lógica baseada em tilesets e uma matriz bidimensional para as informações da fase do jogo. Nós usaremos a Unity com c# como linguagem. Baixe os arquivos fornecidos com este tutorial para acompanhar.

1. O jogo Sokoban

Talvez alguns dos leitores podem não ter jogado essa variante do jogo Sokoban. A versão original pode até ser mais velha que alguns de vocês. Por favor, verifique a página wiki para alguns detalhes. Basicamente, temos um personagem ou elemento controlado pelo usuário, que tem que empurrar caixas ou coisa parecida para chegar a um destino.

O nível consiste em uma grade quadrada ou retangular onde cada casa pode ser não-andável ou andável. Podemos caminhar sobre os quadrados andáveis e empurrar caixas por eles. Casas especiais serão marcados como casas de destino, onde devemos colocar as caixas para completar a fase. O personagem é geralmente controlado usando um teclado. Uma vez que todas as caixas estejam nas casas de destino, a fase está completa.

Desenvolvimento baseado tilesets significa que o nosso jogo será composto de um número de quadrados espalhados de forma predeterminada. Uma espécie de nível representará como cada tile vai espalhar-se para criar a fase. No nosso caso, estaremos usando uma grade quadrada baseada em tileset. Você pode ler mais sobre jogos tileset aqui na Envato Tuts+.

2. Preparando o projeto Unity

Vamos ver como organizaremos o nosso projeto na Unity para este tutorial.

A arte

Para este projeto tutorial, nós vamos usar nenhuma arte externa, mas sim os tipos primitivos de sprite, criados com a mais recente versão da Unity, atualmente 2017.1. A imagem abaixo mostra como podemos criar sprites de formas diferentes dentro da Unity.

How to create sprites within United 20171

Nós usaremos o sprite Square para representar uma única casa da grade da fase do sokoban. Nós usaremos o sprite Triangle para representar nosso personagem, e vamos usar o sprite do Circle para representar uma caixa, ou neste caso, uma bola. As casas normais são brancas, Considerando que teremos cores diferentes para diferenciar.

Os dados da fase

Nós representaremos nossas fases em uma matriz bidimensional que proporciona a perfeita correlação entre a lógica e os elementos visuais. Nós usamos um arquivo de texto simples para armazenar os dados da fase, o que torna mais fácil para editarmos a fase fora da Unity ou alterar as fases simplesmente alterando os arquivos carregados. A pasta Resources tem um arquivo de texto chamado level, que tem a nossa fase padrão.

A fase tem sete colunas e cinco linhas. O valor 1 significa que temos uma casa naquela posição. O valor -1 significa que ele é um não-andável, enquanto que o valor 0 significa que é um quadrado de destino. O valor 2 representa o nosso herói, e 3 representa uma bola empurravel. Só de olhar para os dados da fase, nós pode visualizar como ela ficaria.

3. Criando uma fase do jogo Sokoban

Para manter as coisas simples, e como não temos uma lógica muito complicada, temos apenas um único script Sokoban.cs que está atribuído à câmera de cena. Por favor mantenha-o aberto no seu editor durante o tutorial.

Dados especiais da fase

Os dados da fase representados na matriz 2D são usados não somente para criar a grade inicial, mas também são usados durante todo o jogo para acompanhar as mudanças da fase e o progresso do jogo. Isto significa que os valores atuais não são suficientes para representar alguns dos estados da fase durante o jogo.

Cada valor representa o estado da casa correspondente. Precisamos de valores adicionais para representar uma bola sobre e o herói em cima do quadrado de destino, que são respectivamente -3 e -2. Esses valores podem ser qualquer valor que você atribuir ao script do jogo, não precisa ser necessariamente os mesmos valores que nós usamos aqui.

Transformando o arquivo de texto da fase

O primeiro passo é carregar nossos dados da matriz 2D do arquivo de texto externo. Nós usamos o método ParseLevel para carregar o valor da string dividi-la para preencher nosso leveldata da matriz 2D.

Durante a análise, podemos determinar o número de linhas e colunas que nossa fase terá enquanto populamos nosso levelData.

Desenhando a fase

Quando tivermos os dados da nossa fase, podemos desenhar nossa fase na tela. Nós usaremos o método CreateLevel para fazer isso.

Para nossa fase, nós ajustamos o valor tileSize de 50, que é o comprimento de cada lado do quadrado em nossa grade. Vamos iterar nossa matriz 2D e determinar o valor armazenado nos indíces i e j da matriz. Se esse valor não for um invalidTile (-1) nós criaremos um novo GameObject chamado tile. Atribuímos um componente SpriteRenderer ao tile e um Sprite ou Color, dependendo do valor do índice da matriz.

Para colocar um hero ou um ball, precisamos primeiro criar uma casa e em seguida, criar estes tiles. Como o herói e a bola precisam sobrepor os quadrados do chão, damos ao SpriteRenderer um sortingOrder mais elevado. Todas so tiles tem um localScale ao TileSize atribuido, assim eles têm 50x50 em nossa cena.

Podemos manter o controle de número de bolas em nossa cena usando a variável ballCount, e deve haver o mesmo ou um maior número de casas de destino em nossa fase para tornar possível a conclusão da fase. A mágica acontece em uma única linha de código onde podemos determinar a posição de cada quadrado usando o método GetScreenPointFromLevelIndices (int row, int col).

A posição global de um quadrado é determinado multiplicando-se os índices da fase com o valor de tileSize. A variável middleOffset é usada para alinhar a fase no meio da tela. Observe que o valor row é multiplicado por um valor negativo para ajustar o eixo de y invertido na Unity.

4. Lógica do Sokoban 

Agora que criamos a fase, vamos prosseguir com a lógica do jogo. Precisamos escutar as teclas que o usuário pressiona e mover nosso hero baseado nisso. A tecla pressionada determina a direção do movimento, e nosso hero precisa ser movido nessa direção. Existem vários cenários a considerar após determinar a direção do movimento. Digamos que a casa próxima ao herói nesta direção é tileK.

  • Há uma casa na cena nesta posição, ou está fora da grade?
  • tileK é uma casa andável?
  • tileK está ocupado por uma bola?

Se a posição tileK está fora da grade, Não precisamos fazer nada. Se tileK é válido para andar, então temos que mover o herói até essa posição e atualizar nossa matriz levelData. Se tileK tem uma bola, precisamos considerar o vizinho próximo na mesma direção, digamos tileL.

  • tileL está fora da grade?
  • tileL é uma casa andável?
  • tileL está ocupado por uma bola?

Somente em casos onde o tileL é andável não-ocupado é que devemos mover o herói e a bola em tileK para tileL e asssim respectivamente. Depois de um movimento bem sucedido, precisamos atualizar a matriz levelData.

Funções de apoio

A lógica acima significa que precisamos de saber qual casa o herói está. Também precisamos determinar se uma determinada casa tem uma bola e deve ter acesso a essa bola.

Para facilitar isso, usamos um Dictionary chamado occupants que armazena um GameObject como chave e seus índices de matriz armazenadas como valor Vector2. No método CreateLevel, nós populamos occupants quando criamos um herói ou uma bola. Assim que tivermos o dicionário preenchido, podemos usar o GetOccupantAtPosition para pegar um GameObject dado um determinado índice de matriz.

O método IsOccupied determina se o valor de levelData nos índice fornecido representa uma bola.

Precisamos também de uma maneira de verificar se uma determinada posição está dentro da grade e se a casa é andável. O método IsValidPosition verifica os índices passados como parâmetros para determinar se está dentro da dimensão da fase. Ele também verifica se temos um invalidTile como índice em levelData.

Respondendo aos comandos do usuário

No método Update de nosso script, verificamos os eventos do KeyUp do usuário e comparar com os nossas eventos armazenados na matriz userInputKeys. Quando a direção é determinada, chamamos o método de TryMoveHero passando a direção como um parâmetro.

O método TryMoveHero é onde nossa lógica principal explicada no início é implementada. Por favor leia o seguinte método cuidadosamente para entender como a lógica é implementada conforme explicado anteriormente.

Para obter a próxima posição ao longo de uma determinada direção, com base em uma posição fornecida, usamos o método GetNextPositionAlong. É só uma questão de incrementar ou decrementar um dos índices de acordo com a direção.

Antes de mover o herói ou a bola, é necessário limpar a posição atualmente ocupada na matriz levelData. Isso é feito usando o método RemoveOccupant.

Se encontrarmos um heroTile ou ballTile em determinado índice, precisamos configurá-lo para groundTile. Se encontrarmos um heroOnDestinationTile ou ballOnDestinationTile então precisamos configurá-lo para destinationTile.

Conclusão da fase

O nível está completo quando todas as bolas estiverem em seus destinos.

A Completed Level

Após cada movimento bem sucedido, chamamos o método CheckCompletion para ver se o nível é concluído. Nós iteramos nossa matriz levelData e contamos o número de ocorrências de ballOnDestinationTile. Se esse número for igual ao nosso número total de bolas determinado pelo ballCount, a fase está completa.

Conclusão

Esta é uma implementação simples e eficiente da logica de um sokoban. Você pode criar seus próprios níveis alterando o arquivo de texto ou criando um novo e alterando a variável levelName para apontar para seu novo arquivo de texto.

A implementação atual usa o teclado para controlar o herói. Eu convidaria você para tentar mudar o controle para toques na tela, assim o jogo também pode funcionar em celulares. Isso envolveria encontrar algum caminho 2D, caso deseje que o herói mova-se para a casa que você clicou.

Haverá um tutorial no futuro onde exploraremos como o projeto atual pode ser usado para criar versões isométricas e hexagonais de sokoban com mínimas alterações.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.