Neste vídeo vamos criar o
script de gerenciamento da fase, que irá
gerar proceduralmente nossas fases
para que ela seja diferente cada vez que o jogador joga.
Vamos começar criando dois scripts vazios,
o board manager e o game manager.
Clique em Create - C# Script.
Chame o primeiro BoardManager.
Chame o segundo GameManager.
Vamos também criar um game object vazio
chamado GameManager.
Vamos começar adicionanado nosso código de geração de fase
ao script BoardManager.
Vamos abrir o MonoDevelop.
O script BoardManager irá
criar fases
geradas aleatoriamente cada vez que
o jogador inicia uma nova fase
baseado no número da fase atual.
A primeira coisa que iremos fazer é adicionar
uma declaração de namespace usando System.
Faremos isso para podermos utilizar a
propriedade serialisable.
A propriedade serialisable nos permite
modificar como as variáveis serão exibidas
no inspector e no editor
e para que possamos escondê-las e exibí-las.
Iremos também adicionar
using Systems.Collections.Generic
para que possamos criar listas.
Finalmente vamos incluir
using Random e vamos
configurar-la para ser igual a UnityEngine.Random.
Temos que especificar qual Random usar porque há uma classe
chamada Random tanto no namespace System
como no namespace UnityEngine.
Na nossa classe vamos começar
declarando uma classe pública serialisable chamada Count.
Em Count vamos declarar duas variáveis publicas
chamadas minimum e maximum, ambas do tipo int.
Vamos também incluir uma instrução
de assignment para Count, assim podemos
configurar os valores de minimum e maximum
quando declaramos uma nova instância de Count.
Vamos acrescentar parâmetros Min e Max,
que serão utilizados para configurar os valores de
minimum e maximum.
Em seguida vamos declarar nossas variáveis.
Vamos iniciar com as variáveis públicas,
as duas primeiras serão um inteiro para colunas,
e um inteiro para linhas
Essas variáveis irão limitar as dimensões da nossa fase
e nos permitirão fazer uma fase
maior ou menor simplesmente
mudando as variáveis.
Por enquanto vamos inicializar ambas com o valor oito
isso significa que nossa fase terá um tamanho de oito por oito.
Em seguida vamos usar Count para especificar
um intervalo randômico de quantas paredes
nós queremos criar em cada fase.
nessa caso isso significa que teremos
um mínimo de cinco paredes por fase,
e um máximo de nove paredes por fase.
Vamos fazer o mesmo para nossos items de comida.
Em seguida vamos declarar algumas variáveis
para armazenar os prefabs que iremos gerar
na nossa fase.
Vamos ter um único game object chamado
Exit, pois nossa fase terá somente uma saída.
Para os outros objetos vamos utilizar
vetores, dessa maneira podemos buscar
múltiplos objetos e escolher
qual deles queremos gerar
entre todas as variações.
vamos começar com os tiles do piso.
Faremos o mesmo para wallTiles,
foodTiles, enemyTiles e nosso outerWallTiles.
Vamos preencher cada um desses vetores
com nossos prefabs diferentes para serem escolhidos
no inspector.
Em seguida vamos declarar duas variáveis privadas
incluindo um transform chamado boardHolder.
Vamos usar BoardHolder apenas para manter a
hierarquia limpa, pois vamos
criar vários game objects e vamos torná-los todos filhos
do BoardHolder
para que nós possamos escondê-los na hierarquia
evitando que nossa hierarquia fique cheia de objetos.
Vamos também declarar uma lista privada
de vector3s chamada "gridPos".
Vamos utilizá-la para controlar
todas as possíveis posições
da nossa fase e para controlar se um
já foi criado naquela
posição ou não.
Em seguida vamos declarar uma função chamada
InitialiseList que irá retornar void.
Em InitialiseList vamos começar
limpando nossa lista de grid positions
chamando a função Clear na nossa lista.
Em seguida vamos usar um par de loops aninhados
para preencher a lista
com cada posição da nossa fase
como são vector3, vamos começar com o eixo X.
O primeiro loop irá executar
enquanto o eixo X for menor que o número de colunas.
E em seguida vamos fazer o mesmo para o eixo Y dentro do primeiro loop.
Dentro do loop vamos adicionar
um novo vector3 com os valores dos eixos X e Y
na nossa lista de grid positions.
O que estamos fazendo aqui é criar
a lista de possíveis posições
onde podemos criar paredes, inimigos e pickups.
Estamos faze o loop entre um
e columns - 1
é para deixar uma borda de tiles
diretamente dentro das paredes externas,
assim garantimos que não iremos criar
uma fase impossível de completar.
Em seguida vamos declarar uma nova
função privada que retorna void
chamada BoardSetup.
Vamos usar BoardSetup para
configurar a parede externa e
o piso da nossa fase.
Vamos começar configurando boardHolder para
ser igual ao transform de um novo
game object chamado Board.
Em seguida vamos usar o mesmo padrão
de loop para configurar o piso
e as paredes externas.
Então nós vamos utilizar outro loop for nos
eixos X e Y.
A razão pela qual cada um desses loops
vai de -1 a columns -1 ou rows - 1
é porque estamos criando uma borda
em volta da parte "ativa" da nossa fase
usando os objetos da parede externa.
Em seguida vamos escolher os tiles do piso aleatoriamente
a partir do nosso vetor de tiles
e prepará-los para serem instanciados.
O que estamos fazendo aqui é declarar uma
variável do tipo GameObject
chamada toInstantiate
e configurando-a para ser igual
a um índice da nosso vetor de game objects chamado floorTiles
que estamos escolhendo aleatoriamente
entre 0 e o length do vetor de tiles do piso.
Isso significa que não temos que pré-configurar o length,
podemos simplesmente chamar length e escolher
um número dentro do vetor.
Em seguida vamos verificar
se estamos numa posição que faz parte da parede externa,
e nesse caso vamos escolher um
tile da parede externa para instanciar.
Para isso vamos checar se X é igual a
-1 ou ao valor de columns,
ou se Y é igual a -1 ou o valor de rows
nesse caso vamos instanciar
um tile escolhido aleatoriamente do
vetor de tiles da parede externa.
Uma vez escolhido qual tile queremos
instanciar nós vamos efetivamente instanciar o tile.
Para isso vamos declarar uma variável do tipo
GameObject chamada instance e em seguida vamos configurá-la para
o objeto que estamos instanciando.
Para isso chamamos Instantiate,
passamos como parâmetro o prefab escolhido,
num novo vector3
baseado na nossa coordenada
X e Y atual no loop
e vamos utilizar o valor zero para o eixo Z
pois estamos trabalhando em 2D.
Quaternion.identity significa que o objeto vai ser
instanciado sem rotação
e vamos fazer o cast para game object.
Em seguida configuramos o parent
do nosso recém criado game object
para boardHolder.
BoardSetup vai criar nossos
tiles de parede externa e de piso.
O que faremos em seguida é
começar a escrever algumas funções que irão
colocar objetos aleatórios na fase
como paredes, inimigos e power ups.
Vamos declarar uma nova função
que retorna um vector3 chamada RandomPosition.
Em RandomPosition vamos declarar
um inteiro chamado randomIndex
e gerar um número aleatório dentro de um intervalo.
O intervalo no qual vamos gerar o número
é entre zero
e o número de posições
armazenadas na nossa lista gridPositions,
que iremos acessar usando gridPositions.Count.
Em seguida vamos declarar um vector3
chamado randomPositions e iremos
set it to equal the gridPositions configurar-lo para ser igual a gridPositions
armazenado na lossa lista gridPositions
no índice que sorteamos aleatoriamente.
Para garantir que não iremos gerar dois objetos
no mesmo lugar vamos remover
a posição da nossa lista.
Fazemos isso usando o comando RemoveAt
e passando o índice randomIndex.
Em seguida retornamos o valor de
randomPosition para utilizar-lo
para gerar o objeto numa posição aleatória.
Agora que obtivemos uma posição aleatória
da nossa lista e nos certificamos que ela não é duplicada,
vamos escrever uma função que irá efetivamente
gerar os tiles na posição aleatória escolhida.
Essa função irá chamar LayoutObjectAtRandom
e irá receber três parâmetros.
Um vetor de game objects chamado tileArray,
um inteiro minimum e um inteiro maximum.
A primeira coisa que vamos fazer é
declarar um inteiro chamado objectCount
e inicializar-lo com um valor aleatório entre
minimum e maximum + 1.
objectCount vai controlar
quantas unidades de um dado objeto
vamos criar, por exemplo o número de paredes em uma fase.
Em seguida vamos escrever um loop for.
Vamos repetir este loop for enquanto
i for menos que nosso objectCount,
isso quer dizer que vamos gerar o número de objetos
especificado por objectCount.
Vamos começar escolhendo uma posição aleatória 230 00:11:25,763 --> 00:11:28,480\ chamando a função RandomPosition.
Em seguida vamos escolher um tile aleatório
do nosso vetor de game objects tileArray para gerar o objeto.
Faremos isso gerando um
número aleatório usando Random.Range entre zero e tileArray.length.
Vamos instanciar o tile que
selecionamos na nossa posição aleatória.
Podemos apagar as funções start e update.
E declarar uma nova função pública que retorna void
chamada SetupScene, que recebe um parâmetro do tipo int
chamado Level.
E repare que SetupScene é a única função pública
dessa clase.
Essa é a função que será chamada pelo
game manager quando for o momento de configurar a fase.
Dentro de SetupScene a primeira coisa que vamos fazer
é chamar BoardSetup.
Em seguida vamos chamar initialiseLise.
Vamos chamar LayoutObjectAtRandom
e vamos adicionar nosso vetor de wallTiles
e também os valores minimo e máximo de wallCount.
Em seguida vamos fazer o mesmo para foodTiles.
Ao invés de gerar um número aleatório de inimigos
vamos gerar um número de inimigos
baseado no número da fase
usando MathF.Log
para gerar uma progressão de dificuldade logaritmica.
MathF.Log retorna um número de ponto flutuante (float)
então temos que converte-lo para inteiro.
Isso quer dizer que teremos um inimigo na fase dois,
dois inimigos na fase quatro,
três inimigos na fase oito,
e a dificuldade vai crescendo
conforme o jogador avança de fase.
Agora que temos o número de inimigos que queremos gerar
vamos posicioná-los usando LayoutObjectAtRandom.
Repare que os valores mínimo e máximo nesse
caso são os mesmos porque não estamos especificando um intervalo aleatório.
Finalmente vamos instanciar a saída.
A saída será colocada sempre no mesmo
lugar e sempre será o mesmo objeto
então vamos apenas chamar
Instantiate e passar o prefab Exit.
A saída sempre estará no canto superior direito
da fase, e é por isso que estamos usando
coluna -1 e fila -1
e isso significa que se decidirmos mudar o tamanho
da fase a saída ainda estará no lugar certo.
Vamos salvar o script.
Agora que o código de geração de fase está
escrito no nosso Board Manager
no próximo vídeo iremos começar a escrever
o Game Manager e configurar o Game Manager prefab.