Caso esta seja a primeira vez que você tem contato com um de nossos conteúdos, sinta-se especialmente convidado a nos acompanhar em uma jornada repleta de conteúdos interessantes sobre desenvolvimento de games. Por meio da elaboração de projetos práticos, aprenderemos mais sobre como a ferramenta Unity pode nos ajudar a tirar do papel jogos dos mais variados estilos gráficos e de gameplay.
No momento, estamos elaborando o projeto do jogo Consultório do Dr. Tratanildo, um excêntrico puzzle em que controlamos Tratanildo Doencita, um renomado médico do mundo virtual que trata seus pacientes por meio do uso das miraculosas pílulas de saúde. Trata-se de um jogo ambientado em um consultório médico tridimensional, cujas características de gameplay remetem a clássicos dos videogames, como Pac-Man (Namco, 1980) e Dr. Mario (Nintendo, 1990).
Embora, à primeira vista, os conteúdos possam parecer de difícil assimilação, não se deixe levar por essa percepção inicial: nossa série foi elaborada de forma a permitir a criação de um jogo “a partir do zero” mesmo àqueles que nunca tiveram a oportunidade de interagir com programação de jogos ou de sistemas em geral.
Para isso, a partir do primeiro tópico da série, abordamos desde a instalação da ferramenta Unity em nossos computadores até a configuração e codificação, de fato, das fases, regras do jogo, interações entre elementos e outros aspectos importantes de um game.
Por falar nisso, até o momento já elaboramos outras duas aventuras muito interessantes: o simpático Forest Ping Pong e o platformer 2D Motorista da Pesada. Aproveite o índice disponibilizado no texto inicial da série para conhecê-los e experimentá-los diretamente de seu navegador. Garanto que vale muito a pena!
E então, o que você achou da ideia de trazer à realidade aquele game que você sempre sonhou, mas ainda não tinha ideia de como começar a criar? Se gostou, aperte os cintos e venha conosco em uma divertida trilha, repleta de novos conhecimentos e de muita diversão!
Passando o bastão
Estamos nos dedicando a realizar a interligação entre os diferentes “módulos” de nossa aventura já há alguns encontros, tais como entre as interações iniciais entre pacientes e o médico, e a etapa de exploração do cenário.
Em alguns momentos, realizamos intervenções diretamente no editor do Unity, como realizamos em nosso último encontro ao indicarmos quais seriam as falas e interações iniciais entre os personagens; já em outros, é necessário realizarmos adaptações via código para garantir que haja transferência de informações entre os scripts controladores de cada parte do game.
Na “passagem de bastão” entre a introdução e a ação de fato, uma das principais atividades que o game deverá realizar é a construção adequada dos elementos do labirinto, senão os tratamentos não corresponderão aos casos clínicos identificados pelo nosso renomado médico.
Para entendermos melhor quais serão as intervenções que teremos de realizar, vamos abrir nosso projeto para edição. No Unity Hub, clique duas vezes sobre o item referente ao projeto Consultório do Dr. Tratanildo. Na interface inicial do editor, na aba Project, abra a pasta Assets, Scenes e, por fim, clique duas vezes no ícone da cena ConsultorioScene.
Inicialmente, estamos concentrando as informações sobre as fases por meio de vetores de variáveis presentes em ControllerFase, como podemos notar ao selecionarmos o objeto ControladorFase (via aba Hierarchy) e observarmos os diversos campos a serem preenchidos, visualizáveis por meio da aba Inspector:
Sobre os elementos destacados em azul, nós já realizamos o preenchimento dos conteúdos relativos aos três primeiros pacientes, sendo as falas dos personagens apresentadas de forma adequada em tela durante a introdução da fase. Já sobre os elementos destacados em vermelho, embora seus conteúdos sejam essenciais para a construção dos labirintos e o início dos desafios em si, ainda não iremos preenchê-los, pois devemos configurar antes a interação entre o script controlador da fase e o que rege o labirinto.
Para lembrarmos como o labirinto opera, na aba Hierarchy selecione o objeto Labirinto e, em seguida, observe os atributos do componente Monta Labirinto, exibidos na aba Inspector:
Dá para perceber, pelos nomes dos elementos presentes no controlador do labirinto, que pode haver uma similaridade entre atributos informados ao game via script controlador das fases e os atributos apresentados pelo componente em destaque. O desafio, agora, envolverá realizar a transferência de informação entre os dois scripts, permitindo que os parâmetros de montagem do labirinto respeitem as condições de saúde do paciente em sua respectiva fase.
Para tal, teremos de realizar intervenções nos códigos de ambos os scripts controladores, começando pelo responsável pela configuração do labirinto: na aba Project, abra a pasta Assets e, em seguida, Scripts. Clique duas vezes sobre o ícone que representa o script MontaLabirinto, conforme exemplificado a seguir:
Inicialmente, vamos criar uma rotina para limpar os dados armazenados em vetores de variáveis que servem de base para a montagem do labirinto. Logo após o fechamento das chaves da função Montar(), antes da declaração da função OnEnable(), insira o seguinte trecho de código:
public void LimparValoresVetoresVariaveis()
{
posicoesDoencas = new Vector3[0];
posicoesPilulas = new Vector3[0];
posicoesCubos = new Vector3[0];
dimensoesCubos = new Vector3[0];
doencasReferencia = new GameObject[0];
pilulasReferencia = new GameObject[0];
}
Posteriormente, pouco antes de iniciarmos uma nova fase no jogo, o script controlador de fases irá solicitar ao controlador do labirinto que realize essa limpeza prévia antes de configurar a nova composição que representará o interior do abdômen do paciente.
Para que o script possa realizar a configuração dos tipos de doenças e de pílulas a serem exibidos no interior do paciente após a limpeza dos vetores de variáveis de referência, será necessária a declaração de novas variáveis de referência para cada tipo de agente de doença e de pílula da saúde. Vá ao início do arquivo e, ao final da área destinada à declaração de variáveis, introduza as seguintes linhas de código:
public GameObject pilulaRef_azul, pilulaRef_verde, pilulaRef_roxa, pilulaRef_amarela, pilulaRef_vermelha;
public GameObject doencaRef_azul, doencaRef_verde, doencaRef_roxa, doencaRef_amarela, doencaRef_vermelha;
Vamos, agora, iniciar a codificação da função que, de fato, permitirá a importação das informações declaradas e configuradas via variáveis do controlador de fases para dentro do módulo responsável pela montagem do labirinto. Como o código é extenso, realizaremos sua introdução no script por partes, para que possamos entender melhor o que estará ocorrendo a cada ação declarada.
Importação dos valores
Volte ao trecho final do código e, logo após o fechamento de chaves de LimparValoresVetoresVariaveis(), insira as seguintes linhas de código:
public void ImportarValoresVetoresVariaveis(int faseCorrente, Vector3[] PosicaoMonstrosAzuis, Vector3[] PosicaoMonstrosVerdes, Vector3[] PosicaoMonstrosRoxos,
Vector3[] PosicaoMonstrosAmarelos, Vector3[] PosicaoMonstrosVermelhos, Vector3[] PosicaoObstaculos, Vector3[] DimensaoObstaculos, Vector2[] PosicaoInicialPilulas)
{
// Comandos para importação do tipo e posicionamento
// dos agentes de doenças (monstrinhos)
int i = 0;
foreach (Vector3 posMonstroAzul in PosicaoMonstrosAzuis)
if (posMonstroAzul.z == faseCorrente)
{
System.Array.Resize(ref posicoesDoencas, posicoesDoencas.Length + 1);
System.Array.Resize(ref doencasReferencia, doencasReferencia.Length + 1);
posicoesDoencas[i] = posMonstroAzul;
doencasReferencia[i] = doencaRef_azul;
i++;
}
O primeiro ponto a observarmos é a declaração da função que acabamos de introduzir no código. ImportarValoresVetoresVariaveis receberá como parâmetro um conjunto extenso de vetores de variáveis cujos nomes sugestivamente são semelhantes aos de vetores de variáveis de ControllerFase que terão seus valores preenchidos por nós posteriormente.
Logo após a declaração da função, a primeira ação a ser realizada pelo algoritmo é a importação de informações sobre as posições dos agentes de doença no cenário.
Alguns encontros atrás, quando elaboramos as estruturas das variáveis de ControllerFase, determinamos que o valor Z de cada entrada dos vetores de posição dos agentes de doenças (PosicaoMonstrosAzuis, PosicaoMonstrosVerdes, dentre outros) representaria o paciente ao qual aquele posicionamento do monstrinho dentro do labirinto estaria relacionado. Para ficar mais claro, vamos observar o exemplo a seguir:
- PosicaoMonstrosAzuis[0], PosicaoMonstrosAzuis[1] e PosicaoMonstrosAzuis[2] representantes de monstrinhos que serão exibidos no labirinto do primeiro paciente, de índice 0 (Z = 0);
- PosicaoMonstrosAzuis[3] e PosicaoMonstrosAzuis[4] representantes de monstrinhos referentes ao segundo paciente (Z = 1);
- PosicaoMonstrosAzuis[5] e PosicaoMonstrosAzuis[6] agentes de doenças que serão exibidos apenas para o terceiro paciente (Z = 2).
Considerando que o índice de paciente é representado também pelo valor de faseCorrente, o algoritmo verifica quais entradas referentes aos agentes de doenças azuis fazem parte do “pacote de doenças” daquele paciente, aumentando o tamanho dos vetores posicoesDoencas e doencasReferencia, e inserindo, ao final de cada um dos vetores, referências ao tipo da doença e à posição em que ela deverá ser exibida no labirinto.
Como já sabemos que existem muitas outras variações de doenças além da caracterizada pela cor azul, vamos acrescentar ao código introduzido as seguintes linhas, que realizam ações semelhantes para as demais variantes de agentes de doenças do game:
foreach (Vector3 posMonstroVerde in PosicaoMonstrosVerdes)
if (posMonstroVerde.z == faseCorrente)
{
System.Array.Resize(ref posicoesDoencas, posicoesDoencas.Length + 1);
System.Array.Resize(ref doencasReferencia, doencasReferencia.Length + 1);
posicoesDoencas[i] = posMonstroVerde;
doencasReferencia[i] = doencaRef_verde;
i++;
}
foreach (Vector3 posMonstroRoxo in PosicaoMonstrosRoxos)
if (posMonstroRoxo.z == faseCorrente)
{
System.Array.Resize(ref posicoesDoencas, posicoesDoencas.Length + 1);
System.Array.Resize(ref doencasReferencia, doencasReferencia.Length + 1);
posicoesDoencas[i] = posMonstroRoxo;
doencasReferencia[i] = doencaRef_roxa;
i++;
}
foreach (Vector3 posMonstroAmarelo in PosicaoMonstrosAmarelos)
if (posMonstroAmarelo.z == faseCorrente)
{
System.Array.Resize(ref posicoesDoencas, posicoesDoencas.Length + 1);
System.Array.Resize(ref doencasReferencia, doencasReferencia.Length + 1);
posicoesDoencas[i] = posMonstroAmarelo;
doencasReferencia[i] = doencaRef_amarela;
i++;
}
foreach (Vector3 posMonstroVermelho in PosicaoMonstrosVermelhos)
if (posMonstroVermelho.z == faseCorrente)
{
System.Array.Resize(ref posicoesDoencas, posicoesDoencas.Length + 1);
System.Array.Resize(ref doencasReferencia, doencasReferencia.Length + 1);
posicoesDoencas[i] = posMonstroVermelho;
doencasReferencia[i] = doencaRef_vermelha;
i++;
}
Em seguida, é chegada a hora de realizarmos ações semelhantes para a importação da posição inicial das pílulas de saúde, do posicionamento e da dimensão dos blocos que comporão o labirinto, por meio da inserção do seguinte trecho de código:
// Comandos para importação do posicionamento inicial
// das pílulas de saúde que serão ingeridas pelos pacientes
posicoesPilulas = new Vector3[PosicaoInicialPilulas.Length];
for (i = 0; i < posicoesPilulas.Length; i++)
{
posicoesPilulas[i].x = PosicaoInicialPilulas[i].x;
posicoesPilulas[i].y = 0.5f;
posicoesPilulas[i].z = PosicaoInicialPilulas[i].y;
}
// Comandos para importação da dimensão e do posicionamento
// dos blocos que compõem o labirinto
i = 0;
for (int j = 0; j < PosicaoObstaculos.Length; j++)
if (PosicaoObstaculos[j].z == faseCorrente)
{
System.Array.Resize(ref posicoesCubos, posicoesCubos.Length + 1);
System.Array.Resize(ref dimensoesCubos, dimensoesCubos.Length + 1);
posicoesCubos[i] = PosicaoObstaculos[j];
dimensoesCubos[i] = DimensaoObstaculos[j];
i++;
}
Note que, embora o código referente aos cubos (obstáculos do labirinto) se assemelhe bastante ao código das doenças, os comandos para importação do posicionamento inicial das pílulas obedecem a uma lógica ligeiramente diferente, devido ao fato de que a posição inicial dos medicamentos no labirinto será o mesmo para todos os pacientes e, também, por PosicaoInicialPilulas ser um vetor de variáveis do tipo Vector2, necessitando de algumas adaptações para o correto preenchimento da estrutura de posicoesPilulas, vetor de variáveis do tipo Vector3.
Convertendo coordenadas
Por falar na conversão recém-realizada, talvez você tenha notado que estaremos armazenando os valores bidimensionais nas posições X e Z de cada variável de posicoesPilulas, concedendo o valor 0.5 ao Y de cada variável. Isso deve-se ao fato de que, embora o labirinto aparente ser bidimensional, na realidade trata-se de um conjunto de objetos 3D de nosso cenário sendo observado de cima para baixo, por meio da câmera. A imagem a seguir ilustra o posicionamento da câmera em relação ao labirinto e os eixos tridimensionais abordados:
Valores diferentes para Y podem levar objetos a “atravessarem” o chão do labirinto, levando o objeto a sumir do cenário. Por isso, os valores importados deverão ser alocados majoritariamente para X e Z.
Porém, isso nos leva à segunda questão principal: para variáveis em vetores como PosicaoMonstrosAzuis e PosicaoObstaculos, estamos utilizando o valor de Z para representar o paciente que deverá receber o respectivo agente de doenças ou blocos dentro do labirinto. Como devemos proceder, então, para que os valores informados ao controlador de fases sejam “traduzidos” para o jeito com que MontaLabirinto opera?
Para solucionar essa última questão, acrescente as seguintes linhas após o código recém-introduzido no script:
// Realizar conversão entre coordenadas Y e Z dos valores
// de Vector3 importados até o momento
for (i = 0; i < posicoesDoencas.Length; i++)
{
posicoesDoencas[i].z = posicoesDoencas[i].y;
posicoesDoencas[i].y = 0;
}
for (i = 0; i < posicoesCubos.Length; i++)
{
posicoesCubos[i].z = posicoesCubos[i].y;
posicoesCubos[i].y = 0;
}
for (i = 0; i < dimensoesCubos.Length; i++)
{
dimensoesCubos[i].z = dimensoesCubos[i].y;
dimensoesCubos[i].y = 1;
}
}
Ao realizarmos a concessão dos valores adequados às posições X, Y e Z das variáveis dos vetores utilizados por MontaLabirinto, garantiremos que os objetos apareçam nas posições adequadas, sendo que os valores de Y concedidos foram baseados em experimentações que realizamos anteriormente, durante a elaboração do controlador do labirinto.
Salve o script MontaLabirinto e feche o Visual Studio. Não se esqueça de salvar a cena (menu File, opção Save) e o projeto (menu File, opção Save Project) antes de fechar o editor do Unity.
Próximos passos
Hoje realizamos extensas intervenções em código para possibilitar que, em breve, haja a devida transferência dos valores presentes nos vetores de variáveis do controlador de fases ao módulo que gerencia a montagem do labirinto.
Com as modificações concretizadas, estamos bem mais próximos de observar o real início da aventura, porém ainda serão necessárias intervenções em código e no editor do Unity para podermos ver o módulo funcionar da maneira que planejamos.
Nosso próximo texto já encontra-se disponível, continue conosco nessa jornada de conhecimento e fique ligado sempre aqui no GameBlast!
Revisão: Ives Boitano

















