Como iniciar a desenvolver jogos com o Unity 3D

Este artigo descreve os conceitos inicias para o desenvolvimento de jogos utilizando um motor de jogos (game engine) chamado Unity 3D, utilizando a já conhecida C# como umas das linguagens de programação disponíveis para a criação de scripts.

Esse tema é útil, pois oferece uma alternativa para a criação de jogos eletrônicos, com o intuito de apresentar também a lógica de programação utilizada na criação dos mesmos.

A Unity 3D é um software que possibilita o desenvolvimento de jogos. Assim como toda game engine, ela facilita o desenvolvimento de jogos pelo fato de o desenvolvedor não precisar programar diretamente para DirectX ou OpenGL, pois ela já faz isso automaticamente. A Unity pode fazer jogos para produtos da Apple (Mac, iPhone, iPod, iPad), da Microsoft (Xbox, Windows), da Google (dispositivos com Android), da Sony (Playstation 3), da Nintendo (Wii) e para navegadores Web (Internet Explorer, Mozilla Firefox, Google Chrome, Opera e Safari).

Além dessa portabilidade, a Unity possui uma grande quantidade de ferramentas e é muito fácil de trabalhar com ela, pois além de ser visual (não apenas baseada em código como a Irrlicht, por exemplo) a interface é bastante amigável. Ela possui uma ferramenta de scripts baseada no Mono (ferramenta para desenvolver e executar aplicações .NET em diferentes plataformas), possibilitando a programação em C#, UnityScript e Bool.

A Unity também permite a criação de Shaders com a linguagem SharderLab, Cg da NVidia, GLSL para o OpenGL e trabalha também com Shader Mode. O subsistema de simulação física é o PhysX, também da NVidia. Ela usa também bibliotecas Direct X, Open GL para renderização 3D e OpenAL para áudio.

Outro ponto forte da Unity é a importação de arquivos. Ela importa em vários formatos, tanto 2D quanto 3D, o que exclui o trabalho de ter que exportar alguma arte antes de importar na Unity. Ela aceita os arquivos dos seguintes programas:

  • Blender: .blend;
  • Autodesk 3DS Max: .max;
  • Autodesk Maya: .mb or .ma;
  • Maxon CINEMA 4D: .c4d;
  • Cheetah3D: .jas;
  • Luxology Modo 3D: .lxo - a partir do modo 501;
  • NewTek LightWave: é necessário exportar para .fbx utilizando o plugin para exportar para fbx do LightWave.

Unity também lê arquivos .FBX, .dae, .3DS, .dxf, .obj e também aceita o .collada. Para arquivos de textura (arquivos 2D) ela aceita os formatos: PSD, TIFF, JPG, TGA, PNG, GIF, BMP, IFF, PICT.

O motor gráfico da Unity 3D usa Direct3D (Windows, Xbox 360), OpenGL (Mac, Windows, Linux, PS3), OpenGL ES (Android, iOS) e APIs proprietárias (Wii). Há suporte para mapeamento de relevo, mapeamento de reflexão, mapeamento de parallax, ambient occlusion (SSAO), sombras dinâmicas usando mapas de sombra, render-to-texture e efeitos de pós-processamento.

A Unity suporta conteúdo de arte e formatos de arquivos de 3ds Max, Maya, Softimage, Blender, modo, ZBrush, Cinema 4D, Cheetah3D, Adobe Photoshop, Adobe Fireworks e substância Allegorithmic. Esses assets podem ser adicionados ao projeto de jogo, e utilizados através da interface gráfica da Unity.

A linguagem ShaderLab é usada para shaders, escritos em GLSL e Cg. Um shader pode incluir múltiplas variantes e uma especificação fallback declarativa, permitindo que a Unity detecte a melhor variante para a placa de vídeo atual.

A partir da Unity 4.2, foi adicionado à versão free sombras em tempo real para luzes direcionais e também foi adicionado suporte ao DirectX11, o que dá às sombras uma resolução perfeita dos pixels, texturas para criar objetos 3D a partir de tons de cinza, gráficos, animações faciais mais suaves e um impulso para o frames per second.

A engine também conta com a Unity Asset Server - uma solução de controle de versão para os assets e scripts. Ele usa PostgreSQL como um backend, um sistema de áudio construído sobre a biblioteca FMOD (com capacidade de reprodução de áudio Ogg Vorbis comprimido), reprodução de vídeo usando o Theora codec, um motor de terreno e vegetação (que suporta Tree Billboarding, Occlusion Culling com Umbra), lightmapping embutido e iluminação global com Beast, rede multijogador usando RakNet, e pathfinding mesh navigation embutido.

Além disso, ela também foi construída com suporte para a engine de física Nvidia PhysX (a partir da Unity 3.0) com suporte adicional para a simulação de tecido em tempo real, raycasts e camadas de colisão.

A Unity3D além dessas características é free, apesar de ter a versão Pro que tem um preço não fora da realidade.

Primeiros passos

O exemplo criado para este artigo utilizou a versão 4.2 da Unity 3D, a qual pode ser encontrada no endereço que consta na seção Links. Após instalação, o primeiro passo será criar um projeto para criarmos nosso jogo eletrônico. Para isto, basta clicar sobre o ícone da Unity que veremos o wizard para criação de projetos, conforme Figura 1. Caso a Unity já abra o projeto demostranstrativo - o qual já vem instalado juntamente com o software, selecione no menu superior File -> New Project.

Instalação Unity 3D
Figura 1. Project Wizard

Em seguida, na aba Create New Project, vamos definir uma pasta no sistema para receber todos os arquivos que farão parte do projeto. Seguindo o exemplo deste artigo, iremos criar uma pasta nomeada Point and Click diretamente no desktop.

Perceba que abaixo do campo em que definimos a pasta do nosso projeto temos um conjunto de pacotes com a extensão .unityPackage que podem ser importados. Estes pacotes contêm uma variedade de elementos previamente prontos para construção de um novo jogo, ou seja, diversos objetos que poderão ser utilizados. Como estamos tratando neste artigo dos primeiros passos e contato com esta engine de jogos, não iremos utilizar nenhum deles.

Conceito

Especificamente no ramo de desenvolvimento de jogos temos um momento que chamamos de concept, onde geramos a ideia para o jogo e definimos qual será a mecânica principal do mesmo. No caso do exemplo criado para este artigo, será um point and click, ou seja, a interação do jogador com jogo se dará através do ponteiro do mouse sobre os elementos contidos nas telas do próprio jogo.

Em seguida, com a ideia definida, elaboramos como serão as regras do jogo, descrevendo suas características, juntamente com as condições de vitória e derrota, entre outros - o que podemos chamar de game design, ou seja, as especificações do projeto de jogo.

Primeiramente, vamos definir o High Concept como um jogo de click de mouse em alta velocidade, com o objetivo principal de destruir a maior quantidade possível de objetos no cenário, para acumular pontos e definir uma pontuação final elevada. O controle do jogo será apenas o botão esquerdo do mouse, com o qual apontaremos e destruiremos os objetos em tela. E, por fim, utilizaremos objetos 3D como alvos e efeitos de partículas para destacar a destruição de cada um dos objetos alvos.

A elaboração deste jogo tem a finalidade de abordar os seguintes temas dentro da Unity 3D:

  • Introdução à criação de assets;
  • Introdução à criação de scripts;
  • Introdução à criação de elementos de interface - GUI (Graphic User Inteface);
  • Introdução às entradas de comando de jogo utilizando o mouse - Inputs;
  • Introdução a eventos;
  • Introdução ao carregamento de telas - Scenes.

Interface

O motor de jogos Unity 3D possui uma interface bastante simples e amigável que objetiva facilitar o desenvolvimento de jogos de diversos gêneros e outros sistemas de visualização. Sua área de trabalho é composta de várias janelas chamadas views, cada uma com um propósito específico. A Figura 2 é uma captura contendo a representação esquemática inicial das janelas no editor da Unity3D.

Interface inicial
Figura 2. Interface inicial

Scene View

A Scene View é sua “caixa de areia” interativa. Ela é utilizada para selecionar e posicionar os ambientes, o jogador, as câmeras, inimigos, e todos os outros GameObjects. Manobra e manipulação de objetos dentro da Scene View são algumas das funções mais importantes da Unity, então é importante estar apto a fazê-los rapidamente.

Navegação da Scene View

Para facilitar o uso da Unity 3D, vamos entender alguns fundamentos de navegação na Scene View:

  • Segurando o botão direito do mouse, entra-se no modo flythrough (aéreo), permitindo girar o cenário.
  • Ao selecionar qualquer GameObject e pressionar a tecla F, centralizará o ponto pivô (encontro dos eixos x, y, z) no centro do objeto na Scene View.
  • Segurando Alt e clicando com o botão esquerdo do mouse é possível girar a câmera em torno do pivô do objeto corrente.
  • Segurando Alt e clicando com o botão do meio do mouse é possível arrastar um GameObject pela Scene View.
  • Segurando Alt e clicando com o botão direito do mouse é possível dar zoom na Scene View. A mesma ação pode ser feito utilizando o scroll do mouse.

No canto superior direito da Scene View há o Scene Gizmo. Este mostra a atual orientação da câmera da Scene, e permite modificar rapidamente o ângulo de visão, conforme Figura 3.

Interface inicial
Figura 3. Interface inicial

ToolBar (Barra de Ferramentas)

Disposto na parte superior da interface há um conjunto de ferramentas que nos permite operar e manipular a interface, o jogo e seus elementos, conforme Figura 4.

Toolbar
Figura 4. Toolbar

A barra de ferramentas consiste em cinco controles básicos. Cada um se refere a diferentes partes do editor.

1. Ferramentas de Transformação: usadas com a Scene View.

2. Alternador de Gizmo de Transformação: afeta a tela no Scene View.

3. Botões Play/Pause/Step: utilizados com a Game View (Visão do jogo).

4. Caixa suspensa de camadas: controla quais objetos são mostrados na Scene View.

5. Caixa suspensa de Layout: Controles dispostos em todas as Views.

Se apertarmos no botão Play, poderemos ver a troca entre a Scene View e a Game View, onde surgirá uma tela azul, significando o atual estado do nosso jogo, conforme Figura 5.

Game View
Figura 5. Game View

É habitual no desenvolvimento de jogos verificarmos constantemente como está ficando a visualização do jogo, logo, podemos alterar a interface de trabalho para permitir a visualização das abas Scene e Game simultaneamente.

No canto superior direito da interface, conforme Figura 6, temos um conjunto de opções na janela suspensa Layout, com a finalidade de oferecer outras formas de visualização. Além das opções disponíveis, é possível customizar a interface livremente selecionando qualquer uma das views, arrastando-as e soltando-as sobre a qualquer outra localidade.

Layout
Figura 6. Layout

No sentido de oferecer uma visualização mais fácil para o desenvolvedor, podemos arranjar a interface conforme Figura 6 e por fim salvá-la na opção Save Layout, também incluso na Figura 7. Este novo arranjo das views permitirá visualizarmos as abas Scenes e Game simultaneamente, conforme mencionado anteriormente.

Interface criada
Figura 7. Interface criada

Durante o desenvolvimento do jogo, iremos, conforme a necessidade, conhecer o que são e para que serve cada uma das abas dispostas na interface e também os elementos que podem ser utilizados para a criação de jogos. Começaremos pela aba Project View.

Project View (Visão do Projeto)

Todo projeto Unity contém uma pasta de Assets (ativos - BOX 1). O conteúdo dessa pasta é apresentado no Project View, conforme a Figura 8. Este é local onde são armazenados todos os assets que irão compor o jogo, como cenas, scripts, modelos 3D, texturas, arquivos de áudio e prefabs (pré-fabricados). Se clicarmos com o botão direito em qualquer asset no Project View, é possível selecionar a opção Show In Explorer para realmente ver o asset no sistema de arquivos do computador.

Project
View
Figura 8. Project View

Assets

A palavra "assets" faz referência a todos os ativos para a criação de um jogo eletrônico, ou seja, qualquer item como modelos 3D, texturas, scripts, arquivos de áudio, entre outros.

É recomendado nunca mover os assets do projeto através das pastas do sistema operacional, uma vez que existe a possibilidade de corromper todos os metadados associados ao asset. Logo, sempre se usa o Project View para organizar os assets.

Para adicionar assets ao projeto, podemos arrastar qualquer arquivo do sistema operacional para dentro do Project View, ou usar a opção Assets -> Import New Asset no menu superior da Unity. Desta forma, o asset estará pronto para ser utilizado no jogo.

Scripts

Agora que já conhecemos a aba Project View, podemos criar um script para o projeto. Conforme já comentando, os scripts dentro da Unity podem ser desenvolvidos em UnityScript, C#, ou Boo. É possível utilizar uma ou todas as linguagens de script em um único projeto, pois não há nenhuma penalidade por usar mais de uma.

Criando novos Scripts

Ao contrário de outros assets, como meshs (malhas) ou textures (texturas), arquivos de script podem ser criados a partir de dentro da própria Unity. Para criar um novo script, temos duas opções. A primeira seria clicar no botão Create, posicionado no alto da aba Project, como pode ser visto na Figura 8, e selecionar em seguida C# Script (já que utilizaremos C# como linguagem de programação neste artigo). A segunda opção pode ser encontrada no menu superior da interface, na opção Assets -> Create -> C# Script, conforme Figura 9.

Create
C# Script
Figura 9. Create C# Script

Esta ação irá criar um novo script chamado NewBehaviourScript e colocá-lo na pasta selecionada no Project View. Se nenhuma pasta estiver selecionada no Project View, o script será criado na pasta raiz Assets.

Para editar o script criado, basta clicar duas vezes sobre ele no Project View. Isso iniciará o editor selecionado nas preferências da Unity, que por padrão será o MonoDeveloper. Logo, todos os scripts serão alterados em um editor de texto externo, e não diretamente na Unity. Se preferir o Visual Studio, basta alterar a opção em Edit -> Preferences -> External Tools -> External Script Editor.

Prosseguindo com o projeto, vamos alterar o primeiro script criado com o nome de NewBehaviourScript para ScriptPlayer. Um ponto importante aqui é que ao alterar o nome do arquivo com extensão .cs, é preciso também alterar o nome da classe dentro do arquivo. Os dois devem ter obrigatoriamente o mesmo nome para o Mono compilá-lo corretamente.

Em seguida, vamos criar os seguintes scripts para o projeto, além do já criado ScriptPlayer:

  • ScriptEnemy;
  • ScriptScreenMainMenu;
  • ScriptScreenWin;
  • ScriptScreenLose.

O resultado pode ser visto na Figura 10.

Scripts
Figura 10. Scripts

GameObjects

Agora vamos inserir no projeto alguns objetos para podermos manipulá-los via script. Estes objetos são os GameObjects e são eles os elementos mais importantes na Unity. É muito importante entender o que é um GameObject e como ele pode ser usado.

Cada objeto no jogo é um GameObject. No entanto, GameObjects não fazem nada sozinhos. Eles precisam de propriedades especiais antes que eles possam se tornar um personagem, um ambiente, ou um efeito especial. Mas cada um desses objetos pode fazer muitas coisas diferentes, pois GameObjects são na verdade recipientes. Eles são caixas vazias que podem conter diferentes peças que podem compor uma ilha com lightmap (mapa de luz) ou um carro com direção física. Assim, para realmente compreender GameObjects, é preciso entender outro elemento da Unity chamado Component (Componente). Dependendo do tipo de objeto que será criado, serão adicionadas diferentes combinações de Components para o GameObject.

Podemos imaginar que um GameObject é uma panela vazia e componentes são como diferentes ingredientes que comporão a receita de jogabilidade. Assim, é possível criar nossos próprios Components utilizando Scripts.

A Unity nos oferece alguns GameObjects em forma de primitivas gráficas para prototiparmos ou até mesmo utilizarmos no jogo. Para entendermos onde inseri-los, precisaremos entender a view Hierarchy, conforme Figura 11.

Hierarchy View (Visão da Hierarquia)
Figura 11. Hierarchy View (Visão da Hierarquia)

A aba Hierarchy conterá todos os GameObject (Objeto de jogo) na Scene corrente. Alguns desses são instâncias diretas de arquivos de assets, como modelos 3D, e outros são instâncias de prefabs - objetos customizados que irão compor o jogo. Podemos selecionar e paternizar objetos na Hierarchy. Como objetos são adicionados e removidos da Scene, eles irão aparecer e desaparecer da Hierachy também.

Inicialmente iremos criar dois objetos, uma Sphere (esfera) e um Cube (cubo). Para incluir estes elementos na Hierarchy temos duas possibilidades. A primeira seria clicar no botão Create, posicionado no alto da aba Hierarchy, como pode ser visto na Figura 11, e selecionar em seguida Sphere e Cube. A segunda opção pode ser encontrada no menu superior da interface, na opção GameObject -> Create Other -> Sphere e/ou Cube, conforme Figura 12.

GameObjects
Figura 12. GameObjects

Agora, para melhor entendimento das funções destes objetos, vamos renomeá-los para EnemyCircle(Sphere) e EnemySquare (Cube). Logo em seguida, precisaremos criar “n” instâncias dos dois tipos de inimigos criados, associando a eles futuramente um script para manipulá-los. Para evitar o retrabalho de associar o script para cada GameObject, iremos criar Prefabs.

Criando um Prefab

Prefabs são uma coleção de GameObjects e Components que podem ser reutilizados nas Scenes do jogo. Muitos objetos idênticos podem ser criados a partir de um único Prefab, ou seja, instanciados. Se usarmos como exemplo um modelo 3D de uma árvore e criarmos um Prefab para o mesmo, estaremos permitindo a instanciação de muitas árvores idênticas e colocando-as em diversas Scene diferentes. Isso acontecerá por que o modelo 3D da árvore estará ligado ao Prefab, permitindo que qualquer mudança que for feita diretamente no Prefab seja aplicada automaticamente em todas as instâncias de árvores. Então, se for necessário alterar o mesh (malha), o material, ou qualquer outra coisa, basta realizar a mudança no Prefab e todas as outras árvores herdarão a mudança.

Para realmente criarmos um Prefab a partir de um GameObject da Scene, primeiro criaremos um novo Prefab no Project View. Desta vez temos três possibilidades. A primeira seria clicar no botão Create, posicionado no alto da aba Project, como pode ser visto na Figura 8, e selecionar em seguida Prefab. A segunda opção pode ser encontrada no menu superior da interface, na opção Assets -> Create -> Prefab, conforme Figura 13.

Criando Prefabs
Figura 13. Criando Prefabs

Para o projeto, nomearemos os Prefabs com os mesmo nomes dos dois GameObject adicionados à Scene anteriormente, inserindo a palavra Prefab no início das palavras. Em seguida, podemos arrastar cada um dos GameObjects para seu Prefab relacionado e veremos o texto do nome mudar para azul. A terceira e última opção foi adicionada recentemente nas últimas versões, a qual pode ser feita apenas arrastando os GameObjects da aba Hierarchy para a aba Project. Com isso, temos agora Prefabs reutilizáveis, conforme Figura 14.

Novos Prefabs
Figura 14. Novos Prefabs

Depois de criados os Prefabs, podemos excluir os GameObjects EnemyCircle e EnemySquare da aba Hierarchy e consequentemente da Scene, já que faremos a inclusão de inimigos no jogo dinamicamente via script e utilizando como modelo os Prefabs recentemente criados.

A título de conhecermos softwares de criação de jogos 3D, iremos conhecer agora o termo Material, ou seja, um componente que será anexado a um GameObject, visando oferecer alguma forma de identificação visual, através de cores, texturas e iluminação diferenciada sobre o próprio objeto. Resumindo, assemelhando-se de materiais que podemos encontrar no mundo real.

Vamos criar dois Materials (MatEnemyCircle e MatEnemySquare) para associarmos aos dois Prefabs. Novamente, temos duas opções. A primeira seria clicar no botão Create, posicionado no alto da aba Project (Figura 8) e selecionando em seguida Material. A segunda opção pode ser encontrada no menu superior da interface, na opção Assets -> Create -> Material. Na Figura 15 vemos os materiais que foram criados.

Materials
Figura 15. Materials

Para associarmos Materials aos Prefabs, basta arrastar cada um dos materiais sobre o Prefab correspondente. Em seguida, podemos também arrastar o mesmo script ScriptEnemy sobre os dois Prefabs. Desta forma teremos os GameObjects em forma de círculo e cubo associados a respectivos materiais e a um script através de um Prefab. O resultado pode ser visto na aba Inspector ao selecionarmos qualquer um dos Prefabs, conforme a Figura 16.

Inspector
Figura 16. Inspector

Inspector(Inspetor)

Os jogos feitos na Unity são feitos de múltiplos GameObjects que contêm meshes, scripts, sounds, ou outro elemento gráfico como ligths. O Inspector tem a finalidade de mostrar informações detalhadas sobre o GameObject selecionado no momento, incluindo todos os Components anexados a ele e suas propriedades.

Qualquer propriedade que é mostrada no Inspector pode ser diretamente modificada. Mesmo variáveis de scripts podem ser modificadas sem alteração do próprio script. Pode-se usar o Inspector para mudar variáveis em tempo de execução para experimentar e encontrar a jogabilidade perfeita de um jogo. Em um script, se for definida uma variável pública de um tipo de objeto (como GameObject ou Transform), é possível arrastar e soltar um GameObject ou Prefab dentro do Inspector para fazer a associação. Veremos como fazer isso no decorrer deste artigo.

Agora que temos todos os assets necessários para a construção do jogo, vamos salvar o que temos até aqui dentro de uma Scene.

Scenes

As Scenes, ou seja, as telas do jogo, também são armazenadas no Project View. Pode-se imaginá-las como níveis individuais de um jogo completo. Para salvar a Scene atual podemos clicar no menu superior em File -> Save Scene ou simplesmente através do atalho Ctrl + S e nomeá-la como SceneGameLevel. Qualquer Scene criada para o jogo ficará salva também dentro da pasta Assets, vista na aba Project, conforme Figura 17.

Scene salva
Figura 17. Scene salva

De volta ao projeto, é preciso agora posicionar o objeto Main Camera, na Hierarchy, com as seguintes configurações:

  • Primeiramente devemos clicar sobre o objeto para surgirem seus componente na aba Inspector;
  • Definir os valores do componente Tranform, configurando Position (x: 0, y: 0, z: -10), Rotation (x: 0, y: 0, z: 0) e Size (x: 1, y: 1, z: 1);
  • Abaixo do componente Transform há o componente Camera, o qual é responsável pelo tipo de visão do jogo. Para este artigo utilizaremos a propriedade Projection como Orthographic, Size com valor 5, Clipping Planes com Near 0.3 e Far 20. Sem entrar em muitos detalhes, estas mudanças permitirão trabalhar o jogo com uma visualização 2D em um ambiente 3D.

Lembrar sempre de salvar a cena após alguma alteração nos objetos contidos na mesma (os mesmos listados na aba Hierarchy). O resultado final das alterações na câmera pode ser visto na Figura 18.

Camera
Inspector
Camera Inspectorb>Figura 18. Camera Inspector

Com a câmera configurada, pode-se testar como ficarão os Prefab na tela de jogo (aba Game). Para isso, vamos alterar a opção da combo no canto superior direito da aba Game de Free Aspect para Standalone (1024x768) - definindo as proporções da tela de jogo - e em seguida arrastaremos um dos Prefabs para a aba Hierarchy ou na aba Scene e o posicionaremos nas coordenadas x: 0, y: 0 e z: 0 na propriedade Position que está dentro do componente Transform. Ao final desta ação se pode visualizar o objeto de frente para a câmera, conforme Figura 19, mas percebe-se que o cenário está escuro. Para resolver esse problema devemos adicionar ao cenário um componente de luz, que no caso será um Directional Light. Podemos adicioná-lo da mesma forma que adicionamos a Sphere e o Cube anteriormente: clicando no botão Create da aba Hierarchy ou no menu superior em GameObject -> Create Other -> Directional Light e em seguida ajustá-lo com todos os eixos de Position e Rotation com valor 0. O resultado pode ser visto na Figura 20.

Game View
Figura 19. Game View (sem iluminação)
Directional Light
Figura 20. Directional Light

Agora, vamos inserir alguma ação no jogo. Para isso, vamos abrir o arquivo ScriptPlayer clicando duas vezes sobre ele. Para este artigo, o IDE de programação escolhida foi o próprio MonoDeveloper.

Ao abrirmos o script veremos a estrutura da Listagem 1, onde existirão apenas os métodos Start e Update, os quais serão consumidos automaticamente pelo motor de jogos, ou seja, a preocupação do desenvolvedor será apenas com a lógica de gameplay, não precisando se preocupar com gerenciamento de tempo, memória e renderização gráfica.

Listagem 1. Script C#.

  01 using UnityEngine;
  02 using System.Collections;
  03 
  04 public class ScriptPlayer : MonoBehaviour
  05 {
  06     // Use this for initialization
  07     void Start()
  08     {
  09 
  10     }
  11 
  12     // Update is called once per frame
  13     void Update()
  14     {
  15 
  16     }
  17 }

Através dos comentários sobre os métodos podemos entender que o método Start será chamado somente uma vez - na inicialização da Scene - e o método Update será chamado a cada frame (de acordo com o clock/ciclo de máquina). Então, será através do código script dentro deste método que teremos os objetos do jogo se movimentando e alterando seus comportamentos. Se desejar conhecer todos os recursos deste framework, as referências bibliográficas foram instaladas juntamente com o software. Para acessar, pode-se clicar no menu superior em Help -> Scripting Reference que abrirá no navegador padrão o caminho onde está a documentação.

O PlayerScript conterá a lógica de click do mouse e para isso precisaremos implementar a lógica que recupera o click com o botão esquerdo deste device, conforme a Listagem 2. O script (ou mesmo class) que contém todas as detecções de entradas de teclado, mouse, touch e outros se chama Input. Nele podemos encontrar diversos métodos direcionados para cada tipo diferente de dispositivo de entrada e até mesmo o conjunto de teclas a partir do enum KeyCode.

Listagem 2. Mouse Button Down.

  01 using UnityEngine;
  02 using System.Collections;
  03 
  04 public class ScriptPlayer : MonoBehaviour
  05 {
  06     // Use this for initialization
  07     void Start()
  08     {
  09 
  10     }
  11 
  12     // Update is called once per frame
  13     void Update()
  14     {
  15         // Verificando se foi clicado com o botao esquerdo do mouse.
  16         if (Input.GetMouseButtonDown(0))
  17         {
  18             Debug.Log("O clique funcionou!");
  19         }
  20     }
  21 }

No código da Listagem 2 foi utilizado o método GetMouseButtonDown, pois ele é o correto quando quisermos detectar o ponto final do click do mouse, ou seja, quando ele foi completamente "abaixado". Há outros dois métodos semelhantes: GetMouseButtonUp e GetMouseButton, onde o primeiro detecta quando a tecla do mouse foi totalmente recuada à sua posição original e o segundo detecta se a tecla do mouse está sendo pressionada.

Quanto ao parâmetro 0 (zero) passado no método, ele significa a primeira posição no array de possibilidades do mouse, onde 0 é o botão esquerdo, 1 o botão direito e 2 o botão central.

Se a condição do if for atendida, vamos escrever na aba Console a mensagem "O clique funcionou!". A Console View pode ser vista no canto inferior-direito da Unity na Figura 20. Caso não, pode-se abri-la clicando no menu superior em Windows -> Console ou através do atalho Ctrl + Shift + C. Esta aba é extremamente útil para depuração de código, pois a atualização de aproximadamente 60 vezes por segundo do código inserido no método Update dificulta o melhor uso de breakpoints.

Retornando para a interface da Unity, arrastaremos o script recém-alterado (ScriptPlayer) para sobre o GameObject Main Camera, pois ele funcionará para este exemplo como o jogador. O resultado pode ser visto na parte inferior da Figura 21 com o script fazendo parte dos componentes do objeto da Câmera.

Aplicando um Script ao GameObject
Figura 21. Aplicando um Script ao GameObject

Agora é o momento de testar. Vamos clicar no botão Play no topo da interface e clicar quantas vezes forem necessárias, com isso, visualizando a mensagem sendo escrita na aba Console na mesma quantidade. Desmarque o botão Collapse para ter o mesmo resultado da Figura 22.

Escrevendo na aba Console
Figura 22. Escrevendo na aba Console

Independente onde for feito o click dentro da tela de jogo, a mensagem será escrita igualmente. O próximo passo é identificar o click somente sobre algum dos Prefabs.

Após testar, é importante não se esquecer de clicar no mesmo botão Play para dar Stop, pois qualquer alteração feita na Scene enquanto o jogo estiver em execução será perdida após o Stop, pois alteração em jogo, obviamente, faz parte do gameplay e não de alguma customização que deva ser feita no cenário.

Raycast

Agora será preciso encontrar uma maneira de encontrar um alinhamento entre o local da tela onde foi feito o click do mouse em relação ao fundo do cenário, buscando encontrar algum objeto. Esta ação se chama Raycast, onde é disparada uma linha imaginária a partir de um ponto em direção a algum outro ponto pré-estabelecido, conforme implementação na Listagem 3.

Listagem 3. Raycast.

  01 using UnityEngine;
  02 using System.Collections;
  03 
  04 public class ScriptPlayer : MonoBehaviour
  05 {
  06     // Use this for initialization
  07     void Start()
  08     {
  09 
  10     }
  11 
  12     // Update is called once per frame
  13     void Update()
  14     {
  15         // Verificando se foi clicado com o botao esquerdo do mouse.
  16         if (Input.GetMouseButtonDown(0))
  17         {
  18             Debug.Log("O clique funcionou!");
  19 
  20             // Variável que receberá o objeto que a linha imaginaria 
                 // colidir.
  21             RaycastHit hit;
  22             // Linha iniciada a partir do ponto onde está posicionado 
                 // o mouse.
  23             Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  24 
  25             if (Physics.Raycast(ray, out hit, 100f))
  26             {
  27                 Debug.Log("O clique colidiu com um objeto!");
  28             }
  29         }
  30     }
  31 }
  32

Na nova atualização do script foi inserido o código responsável por identificar quando o click for executado sobre algum objeto. A instância hit criada a partir do script/class RaycastHit será usada com a estrutura out no método Physics.Raycast para o caso da linha imaginária encontrar algum objeto, ou seja, este objeto será preenchido com o GameObejct encontrado no cenário.

A variável ray - instância de Ray - é a linha imaginária criada a partir da localização do mouse. Já que a Unity é um software de construção de jogos, utilizando um ambiente 3D para a criação dos mesmos, todos os objetos do cenário serão posicionados tridimensionalmente, assim, a propriedade Position encontrada em todos os componentes Transform será instância do script/class Vector3 (um vetor de 3 posições: x, y, z). O método ScreenPointToRay fará a conversão do mousePosition (Vector3) para o tipo Ray. Este método é encontrado na propriedade main do script/class Camera, o qual faz referência direta à câmera que estiver na Scene setada com a tag MainCamera, conforme Figura 23.

Tag
MainCamera
Figura 23. Tag MainCamera

A nova estrutura if adicionada ao script está verificando se em uma distância de 100 metros (unidade de medida utilizada pela Unity) foi encontrado algum GameObject. Mais uma vez temos a linha de comando Debug.Log com uma mensagem para podermos testar se está tudo funcionando. Podemos retornar à interface da Unity, clicar em Play e testar novamente o click do mouse sobre a tela de jogo. Caso o click seja executado sobre o objeto, será escrito no Console a mensagem "O clique colidiu com um objeto!", conforme Figura 24.

Console (Testando Raycast)
Figura 24. Console (Testando Raycast)

Como pode ser visto na Figura 23, o uso de uma tag para identificar a câmera, agora será criada uma nova tag chamado Enemy, com o intuito de identificar com apenas uma palavra os dois tipos de inimigos que existem no projeto. Caso houvesse mais do que dois tipos, como é normal em jogos, o uso de tags seria cada vez imprescindível.

Para criar uma nova tag é preciso selecionar algum GameObject presente na Scene ou na aba Project. Já que definiremos uma tag para os inimigos, selecionaremos um dos dois Prefabs e clicaremos na caixa de opções ao lado da palavra tag no Inspector - logo abaixo do nome do objeto -, na qual deve estar aparecendo por padrão a palavra Untagged (sem tag). Ao clicar sobre a caixa surgirão algumas opções previamente criadas pela própria Unity. No caso do projeto deste artigo, deve-se clicar na opção Add Tag, conforme Figura 25.

Add Tag
Figura 25. Add Tag

Ao clicar em Add Tag surgirá no Inspector o TagManager - um local para incluir, editar e excluir tags. O conjunto de tags não mais é do que um array, assim, adicione a palavra Enemy na posição 0 do array (Element 0), conforme Figura 26.

Criando
uma nova Tag
Figura 26. Criando uma nova Tag

Após a criação da tag, clique novamente em um dos Prefabs e selecione a nova tag Enemy. Faça o mesmo para o outro Prefab. O resultado de um dos dois Prefabs de inimigos pode ser visto na Figura 27.

Setando
uma Tag ao GameObject
Figura 27. Setando uma Tag ao GameObject

Retornando ao ScriptPlayer, pode-se identificar agora se o objeto com que raycast colidiu é um dos inimigos através do objeto hit criado anteriormente. Este objeto será uma representação de colisão com um GameObject, logo, podemos encontrar entre suas propriedade o componente Transform e consequentemente a tag, conforme Listagem 4. Se for encontrado o inimigo, vamos criar uma lógica que randomize uma nova posição para o mesmo dentro dos limites possíveis para o tamanho de tela definido, utilizando o script/class Random do namespace UnityEngine.

Listagem 4. Tag e Random.

  01 using UnityEngine;
  02 using System.Collections;
  03 
  04 public class ScriptPlayer : MonoBehaviour
  05 {
  06     // Use this for initialization
  07     void Start()
  08     {
  09 
  10     }
  11 
  12     // Update is called once per frame
  13     void Update()
  14     {
  15         // Verificando se foi clicado com o botao esquerdo do mouse.
  16         if (Input.GetMouseButtonDown(0))
  17         {
  18             Debug.Log("O clique funcionou!");
  19 
  20             // Variavel que recebera o objeto que a linha
                 // imaginaria colidir.
  21             RaycastHit hit;
  22             // Linha iniciada a partir do ponto onde está posicionado
                 //  o mouse.
  23             Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  24 
  25             if (Physics.Raycast(ray, out hit, 100f))
  26             {
  27                 // Verificando se objeto tem a tag Enemy.
  28                 if (hit.transform.tag == "Enemy")
  29                 {
  30                 // Gerando uma nova posição.
  31                     Vector3 newPosition = new Vector3(Random.Range(-6, 6), 
                        Random.Range(-4, 4), 0);
  32                 // Setando para o objeto colidido a nova posição.
  33                     hit.transform.position = newPosition;
  34 
  35                     Debug.Log("O clique colidiu com um inimigo!");
  36                 }
  37             }
  38         }
  39     }
  40 }

Existe na Unity a possibilidade de alterarmos através da interface ou em tempo de execução as variáveis de um script. Para isto, basta declararmos estas variáveis como públicas. Ao ser feito isto, teremos no Inspector onde se encontra o script as variáveis disponíveis para edição, conforme atualização realizada no script (Listagem 5) e visualizada na interface da Unity, conforme a Figura 28. É importante entender que alterações feitas diretamente na interface afetarão o comportamento do script, mas não atualizarão os valores previamente declarados quando da criação das variáveis. Aproveitando a mudança no script, foram retiradas as linhas de escrita no Console e excluído o método Start que não está sendo utilizado.

Listagem 5. Declarando variáveis públicas.

  01 using UnityEngine;
  02 using System.Collections;
  03 
  04 public class ScriptPlayer : MonoBehaviour
  05 {
  06     // Essa variável permitirá a edição da tag via interface.
  07     public string tagName;
  08     // Comprimento da distância do ray que será usado no raycast.
  09     public float rayDistance = 0;
  10 
  11     // Update is called once per frame.
  12     void Update()
  13     {
  14         // Verificando se foi clicado com o botao esquerdo do mouse.
  15         if (Input.GetMouseButtonDown(0))
  16         {
  17             // Variável que recebera o objeto que a linha 
                 // imaginaria colidir.
  18             RaycastHit hit;
  19             // Linha iniciada a partir do ponto onde está
                 // posicionado o mouse.
  20             Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  21 
  22             // Verifica se o ray encontrará algum collider no cenário.
  23             if (Physics.Raycast(ray, out hit, rayDistance))
  24             {
  25                 // Verificando se objeto tem a tag Enemy.
  26                 if (hit.transform.tag == tagName)
  27                 {
  28                     // Gerando uma nova posição.
  29                     Vector3 newPosition = 
                         new Vector3(Random.Range(-6, 6), 
                         Random.Range(-4, 4), 0);
  30                     hit.transform.position = newPosition;
  31                 }
  32             }
  33         }
  34     }
  35 }
Expondo
variáveis
Figura 28. Expondo variáveis

Na Figura 28 é possível visualizar as variáveis criadas como públicas sendo expostas na interface. Com isso, é possível testar diretamente via interface como ficará o jogo. Este é um recurso muito importante para equipes que desenvolvem jogos, pois o programador pode oferecer diversos scripts para um game ou level designer construir as fases do jogo. Assim, quem construirá a fase não precisa obrigatoriamente conhecer programação, somente como utilizar os componentes pré-existentes na ferramenta e o novos que estão sendo criados, por exemplo, o ScriptPlayer. É importante não se esquecer de setar algum valor para a variável rayDistance, pois a mesma não tem valor no código.

Vamos agora abrir o script ScriptEnemy e incrementar valor à jogabilidade: primeiramente vamos alterar a forma de interação dos clicks do mouse, setando que o objeto inimigo em tela muda de posição a cada dois clicks, conforme Listagem 6.

Listagem 6. Script Enemy.

  01 using UnityEngine;
  02 using System.Collections;
  03 
  04 public class ScriptEnemy : MonoBehaviour
  05 {
  06     // Controlora o numero de clicks.
  07     public int numberOfClicks = 2;
  08 
  09     // Update is called once per frame.
  10     void Update()
  11     {
  12         // Se foi clicado mais de duas vezes.
  13         if (numberOfClicks <= 0)
  14         {
  15             // Gerando uma nova posição.
  16             Vector3 newPosition = new Vector3(Random.Range(-6, 6),     
  17             Random.Range(-4, 4), 0);
  18             transform.position = newPosition;
  19 
  20             numberOfClicks = 2;
  21         }
  22     }
  23 }

Agora, o ScriptEnemy no método Update tem uma verificação sobre uma variável pública (numberOfClicks) quantificando os clicks sobre o objeto. Caso tenham ocorrido dois ou mais clicks, será randomizada uma nova posição para o objeto dentro dos limites da tela. Já que a implementação está neste método, a verificação para ver se atende a comparação do if ocorrerá aproximadamente 60 vezes por segundo.

O passo agora é adicionar o ScriptEnemy como novo componente dos Prefabs: PrefabEnemyCircle e PrefabsEnenySquare, simplesmente arrastando o script sobre eles na aba Project. O resultado final pode ser visto no Inspector de um dos dois Prefabs, conforme Figura 29.

ScriptEnemy
aplicado ao Prefab
Figura 29. ScriptEnemy aplicado ao Prefab

Para esta nova implementação funcionar é preciso alterar o ScriptPlayer, pois anteriormente ele estava randomizando a cada click o posicionamento do objeto em tela. A alteração que será realizada neste script detectará a colisão com o objeto, consultará o ScriptEnemy do objeto em questão e decrementará a variável numberOfClicks, conforme Listagem 7.

Listagem 7. Alterando o Script Player.

  01 using UnityEngine;
  02 using System.Collections;
  03 
  04 public class ScriptPlayer : MonoBehaviour
  05 {
  06     // Essa variável permitirá a edição da tag via interface.
  07     public string tagName;
  08     // Comprimento da distância do ray que será usado no raycast.
  09     public float rayDistance = 0;
  10 
  11     // Update is called once per frame.
  12     void Update()
  13     {
  14         // Verificando se foi clicado com o botao esquerdo do mouse.
  15         if (Input.GetMouseButtonDown(0))
  16         {
  17             // Variável que recebera o objeto que a linha 
                 // imaginaria colidir.
  18             RaycastHit hit;
  19             // Linha iniciada a partir do ponto onde está 
                 // posicionado o mouse.
  20             Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  21 
  22             // Verifica se o ray encontrará algum collider no cenário.
  23             if (Physics.Raycast(ray, out hit, rayDistance))
  24             {
  25                 // Verificando se objeto tem a tag Enemy.
  26                 if (hit.transform.tag == tagName)
  27                 {
  28                   ScriptEnemy script = hit.transform.GetComponent
                       <ScriptEnemy();
  29                     script.numberOfClicks--;
  30                 }
  31             }
  32         }
  33     }
  34 }

No ScriptPlayer, anteriormente tínhamos dentro da cláusula if que compara o nome da tag a randomização dos inimigos, mas agora há a recuperação do ScriptEnemy, anexado ao GameObject que foi colidido, através do método GetComponent, o qual consegue recuperar a partir de um GameObject ou Transform qualquer componente. Caso o componente solicitado não faça parte do objeto, a execução do script será interrompida. Um detalhe importante é que dentro dos scripts de programação a variável transform assume a papel de gameObject por muitas vezes, pois o componente Transform é obrigatório e todos os objetos terão, por isso é possível recuperar os componentes do objeto através de métodos da propriedades transform.

Retornando à interface da Unity, vamos posicionar os dois Prefabs na aba Scene, onde o PrefabEnemySquare será posicionado em x: 0, y: 1 e z: 0, enquanto o PrefabEnemyCircle em x: 0, y: -1 e z: 0, resultando no que se pode ver na Figura 30.

Prefabs
no cenário
Figura 30. Prefabs no cenário

Já é possível testar o jogo e verificar que a cada dois clicks sobre cada um dos objetos randomizará uma nova posição para o mesmo.

Será adicionado ao ScriptEnemy um tempo de respawn, ou seja, a cada dois clicks além de randomizada uma nova posição, teremos um tempo de espera para o mesmo reaparecer, conforme pode ser visto na Listagem 8.

Listagem 7. Respawn Enemy.

  01 using UnityEngine;
  02 using System.Collections;
  03 
  04 public class ScriptEnemy : MonoBehaviour
  05 {
  06     // Controlora o numero de clicks.
  07     public int numberOfClicks = 2;
  08     // Tempo para o respawn do inimigo.
  09     public float respawnWaitTime = 2f;
  10 
  11     // Update is called once per frame.
  12     void Update()
  13     {
  14         // Se foi clicado mais de duas vezes.
  15         if (numberOfClicks <= 0)
  16         {
  17             // Disparando uma corotina (thread) para 
                 // gerenciar o tempo de respawn.
  18             StartCoroutine(RespawnWaitTime());
  19 
  20             // Gerando uma nova posição.
  21             Vector3 newPosition = new Vector3(Random.Range(-6, 6),     
  22             Random.Range(-4, 4), 0);
  23             transform.position = newPosition;
  24 
  25             numberOfClicks = 2;
  26         }
  27     }
  28 
  29     // Sera usado para esconder por um tempo o game object.
  30     IEnumerator RespawnWaitTime()
  31     {
  32         renderer.enabled = false;
  33 
  34         yield return new WaitForSeconds(respawnWaitTime);
  35 
  36         renderer.enabled = true;
  37     }
  38 }

O ScriptEnemy recebeu a implementação de um novo método chamado RespawnWaitTime, onde seu retorno é um IEnumerator e dentro dele o componente renderer é inicialmente desabilitado (com isso, o mesh do objeto não será desenhado, tornando-o invisível), em seguida um comando para aguardar durante o tempo definido na variável respawnWaitTime) e por fim habilitar novamente o renderer.

Programando em C# na Unity, quando precisarmos disparar um corotina, é preciso utilizar dos seguintes recursos:

  • Ter um método que retorne IEnumerator, pois dessa forma o compilador entenderá que existe uma sub-rotina sendo executada e a mesma não estará bloqueando o fluxo normal de execução;
  • No método criado deve existir a instrução yield, pois a mesma indicará o uso de um iterador, que no caso é retorno ininterrupto do ciclo de máquina enquanto no método não foi atendido o solicitado. No caso do método criado, seria aguardar por dois segundos. Ao final desse tempo a sub-rotina será "livre" e continuará sua execução nas linhas abaixo;
  • Por fim, para fazer a chamada de um método de corotina, utilizaremos o método StartCourotine passando o método criado, conforme podemos ver na Listagem 7 no método Update, após atender a cláusula if que compara os clicks.

Seguindo adiante, iremos adicionar um pequeno efeito com transição de cores para cada vez que o objeto for randomizado em tela. Aqueles materiais que foram criados no início do artigo e adicionados a cada um dos Prefabs agora serão utilizados para alterarmos as cores dos objetos.

Será criado um novo método - RandomColor - dentro do ScriptEnemy para selecionar uma cor aleatoriamente dentro de um array de cores pré-definidas. A cada respawn do objeto será feita uma chamada para esse método, conforme Listagem 9.

Listagem 9. Random Color.

  01 using UnityEngine;
  02 using System.Collections;
  03 
  04 public class ScriptEnemy : MonoBehaviour
  05 {
  06     // Controlora o numero de clicks.
  07     public int numberOfClicks = 2;
  08     // Tempo para o respawn do inimigo.
  09     public float respawnWaitTime = 2f;
  10     // Quantidade de pontos do inimigo.
  11     public int enemyPoints = 1;
  12 
  13     // Array de cores.
  14     private Color[] shapeColor = { Color.blue, Color.red, 
         Color.green, Color.yellow, Color.magenta };
  15 
  16     // Update is called once per frame.
  17     void Update()
  18     {
  19         // Se foi clicado mais de duas vezes.
  20         if (numberOfClicks <= 0)
  21         {
  22             // Disparando uma corotina (thread) para 
                 // gerenciar o tempo de respawn.
  23             StartCoroutine(RespawnWaitTime());
  24 
  25             // Gerando uma nova posição.
  26             Vector3 newPosition = new Vector3(Random.Range(-6, 6), 
  27             Random.Range(-4, 4), 0);
  28             transform.position = newPosition;
  29 
  30             numberOfClicks = 2;
  31         }
  32     }
  33 
  34     // Sera usado para esconder por um tempo o game object.
  35     IEnumerator RespawnWaitTime()
  36     {
  37         renderer.enabled = false;
  38         yield return new WaitForSeconds(respawnWaitTime);
  39 
  40      // Randomizando a cor.
  41         RandomColor();
  42         renderer.enabled = true;
  43     }
  44 
  45     // Randomizando cores para mudar o material do game object.
  46     private void RandomColor()
  47     {
  48         int index = Random.Range(0, shapeColor.Length);
  49         renderer.material.color = shapeColor[index];
  50     }
  51 }

Na atualização do ScriptEnemy temos um array de cores setado como private e no método RespawnWaitTime a chamada para o novo método. No último comando do método RandomColor temos a alteração da cor do objeto, o qual encontra-se dentro da propriedade material, que está dentro do componente renderer. Então, é importante entender que praticamente tudo que alterarmos via programação pode ser editado via interface e vice-versa. Na Figura 31 podemos visualizar o material que estamos alterando no Inspector do PrefabEnemySquare.

Renderer, material, color
Figura 31. Mesh Renderer -> Material -> Color

Perceba que também foi adicionado um valor padrão de 1 para uma nova variável chamada enemyPoints (utilizaremos a mesma mais à frente). Com a adição do tempo para respawn e randomização de cores, pode-se novamente testar o jogo, o qual terá uma visualização semelhante à Figura 32.

Testando
o jogo com novas cores
Figura 32. Testando o jogo com novas cores

Com o ScriptEnemy finalizado, retornaremos para o ScriptPlayer para criar um contador de pontos para incrementar os clicks realizados corretamente sobre os objetos. Através da inclusão de uma nova variável do tipo int chamada score controlaremos o incremento da pontuação a cada dois clicks do jogador sobre o objeto, conforme Listagem 10, utilizando a variável enemyPoints criada anteriormente. Através da edição desta variável pelo Inspector dos Prefabs, podemos alterar a quantidade pontos para que um ou dois possam valer, ou seja, isso é uma decisão de projeto.

Listagem 10. Calculando o score.

  01 using UnityEngine;
  02 using System.Collections;
  03 
  04 public class ScriptPlayer : MonoBehaviour
  05 {
  06     // Essa variável permitirá a edição da tag via interface.
  07     public string tagName;
  08     // Comprimento da distância do ray que será usado no raycast.
  09     public float rayDistance = 0;
  10     // Contador de pontos.
  11     public int score = 0;
  12 
  13     // Update is called once per frame.
  14     void Update()
  15     {
  16         // Verificando se foi clicado com o botao esquerdo do mouse.
  17         if (Input.GetMouseButtonDown(0))
  18         {
  19             // Variável que recebera o objeto que a linha 
                 // imaginaria colidir.
  20             RaycastHit hit;
  21             // Linha iniciada a partir do ponto onde está 
                 // posicionado o mouse.
  22             Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  23 
  24             // Verifica se o ray encontrará algum collider no cenário.
  25             if (Physics.Raycast(ray, out hit, rayDistance))
  26             {
  27                 // Verificando se objeto tem a tag Enemy.
  28                 if (hit.transform.tag == tagName)
  29                 {
  30                  ScriptEnemy script = hit.transform.GetComponent
                      <ScriptEnemy();
  31                     script.numberOfClicks--;
  32 
  33                     if (script.numberOfClicks == 0)
  34                     {
  35                         score += script.enemyPoints;
  36                     }
  37                 }
  38             }
  39         }
  40     }
  41 }

Adicionaremos ao ScriptPlayer um contador de tempo para o jogo, onde criaremos uma variável do tipo float que será decrementada a cada segundo. Para isso, utilizaremos o método InvokeRepeating do framework, o qual nos permite definir de quanto em quanto tempo um método será chamado, conforme Listagem 11.

Listagem 11. Count Down.

  01 using UnityEngine;
  02 using System.Collections;
  03 
  04 public class ScriptPlayer : MonoBehaviour
  05 {
  06     // Essa variável permitirá a edição da tag via interface.
  07     public string tagName;
  08     // Comprimento da distância do ray que será usado no raycast.
  09     public float rayDistance = 0;
  10     // Contador de pontos.
  11     public int score = 0;
  12     // Contador de tempo para contabilizar quanto tempo durara o jogo.
  13     public float gameTime = 20f;
  14 
  15     void Start()
  16     {
  17         // Definindo o metodo que sera chamado a cada 1 segundo.
  18         InvokeRepeating("CountDown", 1f, 1f);
  19     }
  20 
  21     // Update is called once per frame.
  22     void Update()
  23     {
  24         // Verificando se foi clicado com o botao esquerdo do mouse.
  25         if (Input.GetMouseButtonDown(0))
  26         {
  27             // Variável que recebera o objeto que a linha 
                 // imaginaria colidir.
  28             RaycastHit hit;
  29             // Linha iniciada a partir do ponto onde está posicionado 
                 // o mouse.
  30             Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  31 
  32             // Verifica se o ray encontrará algum collider no cenário.
  33             if (Physics.Raycast(ray, out hit, rayDistance))
  34             {
  35                 // Verificando se objeto tem a tag Enemy.
  36                 if (hit.transform.tag == tagName)
  37                 {
  38                  ScriptEnemy script = 
                       hit.transform.GetComponent<ScriptEnemy();
  39                     script.numberOfClicks--;
  40 
  41                     if (script.numberOfClicks == 0)
  42                     {
  43                         score += script.enemyPoints;
  44                     }
  45                 }
  46             }
  47         }
  48     }
  49 
  50     private void CountDown()
  51     {
  52         // Subtraindo o tempo de jogo.
  53         gameTime--;
  54         // Se chegar a zero, cancela a chamada do metodo.
  55         if (gameTime == 0)
  56         {
  57             CancelInvoke("CountDown");
  58         }
  59     }
  60 }

Se o jogo for testado novamente e estivermos com a Main Camera selecionada na aba Hierarchy, poderemos ver na aba Inspector a variável Game Time sendo decrementada a cada segundo até chegar a zero.

Nas definições do que é jogo encontraremos que ele é feito de feedbacks visuais e aurais. No caso dos visuais, seriam todas as imagens projetadas na tela de jogo que ofereçam ao jogador o status do jogo ou mesmo alertas sobre acontecimento que acontecerem ou estão por vir. Então, criaremos dentro do ScriptPlayer um GUI (Graphic User Interface) para escrever em tela o valor atual do score e do tempo restante de jogo.

Todas as escritas de textos que envolvem GUI serão executadas dentro de um método específico chamado OnGUI, conforme Listagem 12.

Listagem 12. Escrevendo na GUI.

  01 using UnityEngine;
  02 using System.Collections;
  03 
  04 public class ScriptPlayer : MonoBehaviour
  05 {
  06     // Essa variável permitirá a edição da tag via interface.
  07     public string tagName;
  08     // Comprimento da distância do ray que será usado no raycast.
  09     public float rayDistance = 0;
  10     // Contador de pontos.
  11     public int score = 0;
  12     // Contador de tempo para contabilizar quanto tempo durara o jogo.
  13     public float gameTime = 20f;
  14 
  15     void Start()
  16     {
  17         // Definindo o metodo que sera chamado a cada 1 segundo.
  18         InvokeRepeating("CountDown", 1f, 1f);
  19     }
  20 
  21     // Update is called once per frame.
  22     void Update()
  23     {
  24         // Verificando se foi clicado com o botao esquerdo do mouse.
  25         if (Input.GetMouseButtonDown(0))
  26         {
  27             // Variável que recebera o objeto que a linha 
                 // imaginaria colidir.
  28             RaycastHit hit;
  29             // Linha iniciada a partir do ponto onde está posicionado 
                 // o mouse.
  30             Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  31 
  32             // Verifica se o ray encontrará algum collider no cenário.
  33             if (Physics.Raycast(ray, out hit, rayDistance))
  34             {
  35                 // Verificando se objeto tem a tag Enemy.
  36                 if (hit.transform.tag == tagName)
  37                 {
  38                     ScriptEnemy script = hit.transform.GetComponent
                         <ScriptEnemy();
  39                     script.numberOfClicks--;
  40 
  41                     if (script.numberOfClicks == 0)
  42                     {
  43                         score += script.enemyPoints;
  44                     }
  45                 }
  46             }
  47         }
  48     }
  49 
  50     private void CountDown()
  51     {
  52         // Subtraindo o tempo de jogo.
  53         gameTime--;
  54         // Se chegar a zero, cancela a chamada do metodo.
  55         if (gameTime == 0)
  56         {
  57             CancelInvoke("CountDown");
  58         }
  59     }
  60 
  61     void OnGUI()
  62     {
  63         GUI.Label(new Rect(10, 10, 120, 40), "Score: " + score);
  64         GUI.Label(new Rect(10, 30, 120, 40), "Time: " + gameTime);
  65     }

No método OnGUI, que será executado na mesma parcela de vezes que o Update, estaremos escrevendo na tela dois labels. O primeiro deles é a pontuação do jogo e o segundo o tempo de jogo. Para definirmos em tela algum tipo de GUI, precisamos sempre definir em que posição ele estará e o espaço que ocupará; isso é feito através do script/class Rect, ou seja, definimos um retângulo em tela a partir da esquerda e do alto e depois a largura e altura. Algo semelhante ao CSS (Cascading Style Sheet) utilizado na web.

Após salvar a Scene atual (SceneGameLevel), vamos criar uma nova cena que será o menu inicial do jogo clicando no menu superior em File -> New Scene, conforme Figura 33.

New Scene
Figura 33. New Scene

Nesta nova cena faremos o seguinte para prepará-la para ser o menu inicial:

  • Selecionar a Main Camera e editá-la no Inspector da mesma forma que editamos a câmera na SceneGameLevel, conforme Figura 18. O resultado da câmera na aba Scene será um visão ortográfica, de tamanho 5 e distância 20, conforme Figura 34;
  • Anexar o script nomeado como ScriptScreenMainMenu à câmera, apenas arrastando-o sobre o objeto;
  • Abrir o ScriptScreenMainMenu para edição no MonoDevelop, onde o resultado será o apresentado na Listagem 13.
Scene View
Figura 34. Scene View -> Main Camera
Listagem 13. ScriptScreenMainMenu.

  01 using UnityEngine;
  02 using System.Collections;
  03 
  04 public class ScriptScreenMainMenu : MonoBehaviour
  05 {
  06     void OnGUI()
  07     {
  08         if (GUI.Button(new Rect(10, 10, 120, 40), "Start Game"))
  09         {
  10             Application.LoadLevel("SceneGameLevel");
  11         }
  12         if (GUI.Button(new Rect(10, 70, 120, 40), "Exit Game"))
  13         {
  14             Application.Quit();
  15         }
  16     }
  17 }

Novamente temos o uso do método OnGUI, onde foram incluídos dois botões em tela. Os dois são posicionados através do script/class Rect e setados seus valores no segundo parâmetro do método: Start Game e Exit Game. O método GUI.Button executa duas funções: uma delas seria a de escrever o texto no botão e a segunda seria a detecção de algum click sobre ele. No caso do click no botão Start Game, teremos a chamada ao método Application.LoadLevel que carregará a scene setada no seu parâmetro. No caso de click no botão Exit Game teremos a chamada ao método Apllication.Quit, que fecha o jogo.

Depois de concluído esta etapa, devemos salvar a nova cena (Ctrl + S), nomeando-a SceneScreenMainMenu, conforme Figura 35.

Screen main menu
Figura 35. Scene Screen Main Menu

Antes de testarmos, vamos nos adiantar e criar duas novas cenas para o jogo:

  • SceneScreenWin;
  • SceneScreenLose.

Elas serão utilizadas para representar vitória e derrota do jogador. Crie-as conforme foram criadas as anteriores, editando suas câmeras e anexando os scripts ScriptScreenWin e ScriptScreenLose às câmeras de suas cenas respectivas.

Agora sim, para testarmos a transição entre as telas, precisamos entender o processo de build da Unity.

Builds

Em qualquer momento da criação do jogo se pode ver como o jogo ficará no seu formato final, ou seja, podemos testá-lo fora do editor da Unity. Através do menu superior File -> Build Settings teremos acesso a uma janela de ajuda, onde podemos definir para qual plataforma será realizado o build do jogo, conforme Figura 36.

Build Settings
Figura 36. Build Settings

A primeira vez que for acessando esta janela em um projeto, ela irá aparecer com Scenes in Build vazia. Se o jogo for construído com essa lista vazia, apenas a Scene que estiver aberta no momento será incluída no Build. Se desejarmos construir rapidamente uma execução de teste com apenas um único arquivo Scene, bastar construir com a lista de Scene vazia mesmo.

Para adicionar os arquivos de Scene à lista para builds, basta selecioná-la na aba Project e arrastá-la para dentro da caixa Scenes in Build, conforme Figura 37.

Scenes in Build
Figura 37. Scenes in Build

Nesse ponto, observe que cada uma das Scenes tem um valor diferente de índice. A Scene 0 será a primeira Scene que carregada quando o jogo for executado fora o editor da engine. Como já mostrado anteriormente, para carregar uma nova Scene, utiliza-se o comando Application.LoadLevel a partir de algum scripts.

Se for adicionado mais de um arquivo de Scene e for necessário reorganizá-los, basta simplesmente clicar e arrastar para cima ou para baixo na lista até que se tenha a ordem desejada.

Para remover uma Scene da lista, clique para destacar a Scene e pressione a tecla Delete. A Scene irá desaparecer da lista e não será incluída no build.

Quando o jogo estiver pronto para ser publicado, selecionaremos uma Platform (Plataforma). Finalmente pressionaremos o botão Build (Construir). Em seguida, selecionaremos o nome e a localização do jogo usando uma janela de salvar arquivos padrão. Quando clicarmos em Save (Salvar), a Unity irá construir o jogo prontamente.

Para testar a transição de Scenes também é preciso adicioná-las na caixa de Scenes In Build. Após isso, clique no Play e teste clicar no botão Start Game e ver o jogo carregar a SceneGameLevel.

Para finalizar o jogo vamos implementar o scripts de vitória e derrota que serão utilizados nas últimas duas Scenes criadas, conforme Listagens 14 e 15.

Listagem 14. ScriptScreenWin.

  01 using UnityEngine;
  02 using System.Collections;
  03 
  04 public class ScriptScreenWin : MonoBehaviour
  05 {
  06     void OnGUI()
  07     {
  08         GUI.Label(new Rect(10, 10, 120, 40), "YOU WIN!!!");
  09 
  10         if (GUI.Button(new Rect(10, 70, 120, 40), "Restart Game"))
  11         {
  12             Application.LoadLevel("SceneGameLevel");
  13         }
  14         if (GUI.Button(new Rect(10, 140, 120, 40), "Exit Game"))
  15         {
  16             Application.Quit();
  17         }
  18     }
  19 }
Listagem 15. ScriptScreenLose.

  01 using UnityEngine;
  02 using System.Collections;
  03 
  04 public class ScriptScreenLose : MonoBehaviour
  05 {
  06     void OnGUI()
  07     {
  08         GUI.Label(new Rect(10, 10, 120, 40), "YOU LOSE!!!");
  09 
  10         if (GUI.Button(new Rect(10, 70, 120, 40), "Restart Game"))
  11         {
  12             Application.LoadLevel("SceneGameLevel");
  13         }
  14         if (GUI.Button(new Rect(10, 140, 120, 40), "Exit Game"))
  15         {
  16             Application.Quit();
  17         }
  18     }
  19 }

Perceba que os códigos são semelhantes ao script do menu, apenas com a adição de um label com a mensagem de vitória ou derrota, a reestruturação da altura inicial do posicionamento dos botões e o primeiro botão como Restart Game.

Com todas as Scenes implementadas, vamos retornar ao ScriptPlayer e adicionar ao final do tempo de jogo uma condição de verificação para o jogador vencer ou perder o jogo, conforme Listagem 16.

Listagem 16. Condição de vitória e derrota.

  01 using UnityEngine;
  02 using System.Collections;
  03 
  04 public class ScriptPlayer : MonoBehaviour
  05 {
  06     // Essa variável permitirá a edição da tag via interface.
  07     public string tagName;
  08     // Comprimento da distância do ray que será usado no raycast.
  09     public float rayDistance = 0;
  10     // Contador de pontos.
  11     public int score = 0;
  12     // Contador de tempo para contabilizar quanto tempo durara o jogo.
  13     public float gameTime = 20f;
  14     // Quantidade de pontos para a vitória.
  15     public int numberOfPointsToWin = 10;
  16 
  17     void Start()
  18     {
  19         ...
  20     }
  21 
  22     // Update is called once per frame.
  23     void Update()
  24     {
  25     ...
  26     }
  27 
  28     private void CountDown()
  29     {
  30         // Subtraindo o tempo de jogo.
  31         gameTime--;
  32         // Se chegar a zero, cancela a chamada do metodo.
  33         if (gameTime == 0)
  34         {
  35             CancelInvoke("CountDown");
  36 
  37             if (score >= numberOfPointsToWin)
  38             {
  39                 Application.LoadLevel("SceneScreenWin");
  40             }
  41             else
  42             {
  43                 Application.LoadLevel("SceneScreenLose");
  44             }
  45         }
  46     }
  47 
  48     void OnGUI()
  49     {
  50         ...
  51     }
  52 }

No método CountDown, ao final do tempo de jogo, foi adicionada uma comparação onde a pontuação obtida for igual ou superior à quantidade necessária (nova variável numberOfPointsToWin) será carrega a tela de vitória, do contrário a tela de derrota.

Se desejar, na opção Build Settings, faça o build do jogo em diferentes plataformas e veja o resultado.

Enfim, temos nosso jogo finalizado. Por mais simples que seja o gameplay do jogo construído, a intenção deste artigo foi a de apresentar a ferramenta e os conceitos que envolvem a criação de um jogo. Ainda, explorar um pouco do C# para o desenvolvimento na Unity.

Conclusão

A Unity 3D é uma poderosa ferramenta para criação de jogos e com ela vem uma infinidade de recursos para elaboração dos mesmos. Este artigo teve o intuito de apresentar para os apaixonados por jogos que é possível criar os seus próprios com a ajuda de uma interface amigável, que nos facilita em diversos pontos do desenvolvimento, caso quiséssemos fazer tudo “na mão”. Toda a parte de LoopGame, desenho e renderização gráfica já está embutida no software, além da outros recursos não abordados aqui, como sistemas de partículas, efeitos de luz e sombra, áudio, entre outros. A área de criação de jogos no Brasil ainda engatinha, mas se for do interesse do leitor de ingressar nesse ramo ou pelo menos conhecer, existem diversos tutoriais disponíveis pela internet e com pouco esforço é possível encontrar alguns bastante profissionais.

Links Úteis

  • Site do Unity 3D:
    Comece trazendo sua visão para a vida hoje. A plataforma de desenvolvimento 3D em tempo real da Unity permite que você tenha tudo o que precisa para criar, operar e gerar receita.
  • Download Unity 3D:
  • O que é Unity 3D?:
    Neste curso apresentaremos os conceitos introdutórios do Unity3D, suas características e funcionamento. Veremos também um exemplo prático utilizando o Unity3D.

Saiba mais sobre Unity 3D ;)