Essa aula irá mostrar como escrever e executar um código no Unity.
A linguagem padrão usada pelo Unity é o C#, e, numa abordagem introdutória, todo código criado funciona como um componente personalizado pelo programador. Em outras palavras, o código que um usuário do Unity cria deve ser adicionado a um objeto pelo Inspector para dar a esse objeto a possibilidade de executar as ações que o programador programou.
Como plano de fundo dessa aula, iremos trabalhar em cima de um código simples de movimento 8 direções em um projeto 2D.
Regras dos códigos
Para criar um código, clicamos com o botão direito no espaço da pasta Assets, e seguimos o caminho Create -> C# Script.
Quando o Unity cria o código dentro da pasta Asset, ele automaticamente habilita a edição do nome do código. Esse nome deve respeitar as mesmas regras de declaração de variáveis - não pode conter espaços, não pode começar com números e nem conter caracteres especiais (acentos, símbolos, entre outros).
Darei ao código o nome de SimpleMove. Depois de renomear o código, podemos abri-lo clicando duas vezes nele.
Geralmente o editor de texto mais usado para códigos do Unity é o Visual Studio Community. Ele nos serve muito bem pois o atalho de auto completar (control + espaço) facilita muito para encontrar os atributos e métodos dos códigos. Contudo, vale lembrar que código nada mais é que um arquivo de texto, e, portanto, podemos usar qualquer outro editor de texto para escrever nossos códigos - só precisamos arrastar o código da pasta Asset para dentro do editor.
De qualquer maneira, uma vez que o código esteja aberto, repare que o nome da classe é a mesma do nome do código. Se os nomes forem diferentes o Unity terá problemas para gerenciar o código, então sempre garanta que ambos os nomes sejam totalmente idênticos.
Do lado do nome da classe está a herança para a classe MonoBehaviour. Essa classe é própria do Unity e é a responsável por fazer com que nosso código possa ser adicionado a um objeto como componente e execute uma série de métodos nativos do Unity (como Start(), Update(), métodos de colisão, entre outros).
É possível testar isso tirando essa herança - automaticamente o componente vai bugar ou mudar para um nome diferente no Inspector, e provavelmente o Unity exibirá algum log de erro no Console. Sem o MonoBehaviour então, nossa classe será uma classe comum do C# e não terá relação direta com o Unity.
Dentro da classe, o Unity gera dois dos principais métodos de execução de código, o Start() e o Update(). Neles que escrevemos os códigos que queremos que o Unity execute quando o jogo estiver rodando.
O Start() executa apenas uma vez antes do primeiro frame do jogo. O Update() executa uma vez por frame.
Então, por exemplo, se queremos iniciar o jogo com o personagem com a vida cheia, definimos essa vida no Start(), tendo em vista que o início do jogo só acontece uma vez na cena, que é o momento em que o Start() é rodado.
Se queremos mover o personagem toda vez que o jogador movimenta o direcional do controle ou aperta uma tecla, colocamos esse código no Update(), porque ao longo do jogo existe sempre a possibilidade do jogador mover o personagem, ou seja, é algo que pode acontecer ao longo de diversos frames, até o fim do jogo, e em cada frame o Update() é chamado uma vez.
Para escrever o movimento de um objeto podemos partir da fórmula de movimento uniforme da física - S = S0 + V.T. Essa fórmula nos diz que a próxima posição de um objeto é igual a posição atual mais o deslocamento (vetor velocidade) vezes o tempo.
A posição atual do objeto é acessada através do comando transform.position; o tempo no jogo é representado pelo intervalo dos frames, acessível através do Time.deltaTime.
Por fim, o vetor velocidade pode ser desmembrado em direção de movimento e rapidez (velocidade escalar).
Declarando atributos
Para escrever a fórmula de movimento, as únicas informações que devemos criar serão a direção e a rapidez do objeto.
Rapidez determina o tamanho do espaço que o objeto se move a cada frame. A direção determina a orientação do movimento do objeto. Como estamos falando de um jogo 2D, o objeto se move em dois eixos, X e Y, portanto a direção é um Vector2, uma struct nativa do Unity.
O Unity é conhecido por aliar muito bem o código com a interface do programa. Ao definir um atributo podemos tornar esse atributo editável pela interface.
Quando o atributo é protegido ou privado, ele não aparece no Inspector. Quando o atributo é público ou é marcado pela diretiva SerializeField, ele se torna visível e editável pelo Inspector (dentro do componente).
Um detalhe é que o valor que aparece no Inspector tem prioridade sobre o valor colocado na declaração do atributo. Por causa disso vou deixar a rapidez com a diretiva SerializeField para não precisar voltar ao código só para isso. A direção vou manter privada.
Acessando outros componentes
O próximo passo é acessar a posição do objeto que executará o movimento 8 direções. Na aula 1 vimos que essa informação faz parte do componente Transform, então precisamos acessar esse componente por código.
Os métodos de assinatura GetComponent<>() são um conjunto de métodos que servem justamente para isso, acessar componentes por código. O código a seguir mostra alguns exemplos de como usar esse tipo de método. Repare que o GetComponent<>() retorna o componente armazenando-o numa variável, e é a partir dessa variável que manipulamos o componente.
Para ficar claro, precisaríamos usar o GetComponent<>() para acessar o Transform e ler a posição do objeto. "Precisaríamos" porque na verdade não precisamos. O Transform é um componente especial, ele é o único componente obrigatório em todo e qualquer objeto de cena do Unity.
Não existem GameObjects sem o Transform, sendo assim, o Unity disponibiliza uma variável de acesso a ele pronta para uso, o transform (maiúsculo é a classe, minúsculo é a variável). Então, especificamente nesse caso, não precisamos do GetComponent<>(), apesar de que ele funciona perfeitamente para acessar o Transform.
De qualquer forma, transform.position retorna a posição do objeto que contém o código (SimpleMove no caso). E por fim, o tempo, como dito antes, é acessado pelo Time.deltaTime.
Até aqui então, convertendo a fórmula matemática para código, temos o seguinte:
Vamos a algumas considerações para situar melhor o código acima:
Eu retirei o método Start() porque ele não será utilizado, já que não precisaremos inicializar nada. O fato de ter um método Start() declarado, ainda que vazio, faz com que o Unity o rode, consumindo processamento desnecessário;
O código da fórmula de movimento é colocado dentro do Update() porque o objeto irá se mover ao longo de vários frames, controlado pelo usuário, ou seja, é uma ação que pode executar a qualquer momento durante a execução do jogo, e várias vezes durante a execução do jogo. Isso significa que o movimento do objeto é uma atualização do mundo do jogo (daí o nome "Update");
direction.normalized serve para garantir o controle preciso da rapidez do objeto. Mais detalhes se encontram nas aulas de Vetores em Programação de Jogos.
(Vector3)direction é o que chamamos de conversão ou cast. Por definição, todo transform.position é um Vector3, assim, para que a soma "position + direction" seja possível, convertemos momentaneamente a variável direction para o tipo Vector3.
E, para finalizar o código, precisamos ler os comandos do jogador, os "inputs". Veremos em mais detalhes como gerenciar esses comandos, configurar teclas e ler tipos de "inputs" diferentes em outra aula. Por enquanto vamos ficar apenas na leitura dos comandos direcionais.
Input.GetAxis() é o método que retorna o valor de controle de um eixo, em valores de 1 a -1. Isso quer dizer que dado um eixo (Horizontal ou Vertical), o método vai dizer se o jogador está querendo mover o personagem em sentido negativo ou positivo.
Então, por exemplo Input.GetAxis("Horizontal") retorna o controle de um objeto no eixo X, e, se o valor for -1, o usuário esta querendo mover o objeto para a esquerda (usando a tecla A ou movendo o direcional para a esquerda); se o valor for 1, é o caso contrário, usando a tecla D e movendo o objeto para a direita. Simplificando temos:
Veja, o Input.GetAxis() apenas lê o controle do jogador, para mover o objeto de fato iremos usar os valores lidos para criar o nosso vetor de direção. Segue o código:
Testando o código
Até aqui só mexemos com o código. O próximo passo é criar um objeto e usar o código para move-lo.
Como estamos lidando com um movimento 2D (em um projeto 2D), usarei uma sprite como objeto.
Depois de criar a sprite, podemos adicionar o código no objeto de três principais formas:
Arrastando o código para cima do objeto no Hierarchy;
Selecionando o objeto no Hierarchy e arrastando o código para a lista de componentes no Inspector;
Adicionando o código usando o botão Add Component no Inspector;
Se o código não contém erros e já foi totalmente processado, o código aparecerá normalmente na lista de componentes. Se o código conter qualquer tipo de erro ou ainda estiver sendo processado, um aviso aparecerá ao tentar adiciona-lo ao objeto.
Enfim, depois de adicionarmos o código no objeto (eu criei um círculo como sprite), você verá que o speed aparecerá como campo para edição. Comece testando com speed igual a 8.
Ao dar Play você verá que o objeto estará parado (afinal, ele só se move quando apertamos os botões), e você poderá mover o objeto pela cena usando as teclas WASD ou as setas.