Baixe o app para aproveitar ainda mais
Prévia do material em texto
Configurando o projeto e movimentando o inimigo Porque usar um game engine? A maior dificuldade enfrentada quando queremos começar a desenvolver jogos é justamente descobrir por onde começar. Existem muitas maneiras diferentes de começar e a quantidade de informação é tão grande que acabamos nos perdendo em meio a tantas possibilidades. Ainda assim, quando vencemos esse primeiro obstáculo, escolhemos uma linguagem de programação e iniciamos o desenvolvimento, logo percebemos que precisamos escrever muito código para realizar tarefas essenciais como desenhar imagens na tela, emitir sons, capturar entrada através de gamepads, teclado ou mouse, entre outras. Com isso, acabamos perdendo muito tempo trabalhando para preparar toda essa base antes mesmo de começar a desenvolver a ideia do nosso jogo. É justamente nessa fase que a maioria dos aspirantes a desenvolvedores de jogos acaba desistindo. Uma boa notícia é que muitos desenvolvedores já passaram por isso antes e decidiram resolver esse problema para facilitar o desenvolvimento de seus próximos jogos ou para ajudar outros desenvolvedores. Como essas tarefas são bastante comuns e como praticamente todos os jogos fazem uso delas, faz sentido escrever o código para todas elas uma única vez e disponibilizar esse código em forma de bibliotecas. Assim, utilizando essas bibliotecas podemos começar a codificar diretamente as regras do nosso jogo sem nos preocuparmos com as tarefas mais básicas. Quando reunimos esse conjunto de bibliotecas com o objetivo de fornecer um framework para a execução de jogos, estamos criando o que chamamos de um game engine. Um game engine (motor de jogos) é capaz de executar vários tipos de jogos diferentes assim como um motor pode ser usado para mover os mais diversos objetos como carros, esteiras, hélices e turbinas. Geralmente, um game engine possui um conjunto de subsistemas que são responsáveis por tipos específicos de tarefas. Os principais subsistemas de um game engine são apresentados abaixo: Renderização 2D/3D - responsável por todas as tarefas relacionadas ao desenho de objetos, efeitos especiais, vídeos na tela, entre outros. Áudio - responsável pela emissão de efeitos sonoros a partir de arquivos às vezes utilizando recursos mais avançados como sons posicionais 3D. Física - responsável pela simulação da movimentação dos objetos de um jogo com base nas leis da física como conhecemos. Colisões - responsável pela detecção de colisão entre os objetos de um jogo. Scripts - responsável pela interpretação de scripts contendo regras ou comportamento de objetos do jogo e que facilitam a customização por desenvolvedores, designers e artistas. Entrada - responsável pela captura dos comandos de dispositivos de entrada como teclado, mouse, gamepads e sensores diversos. Hoje existem muitos game engines disponíveis para quem deseja desenvolver um jogo. Dentre os mais conhecidos podemos citar o Unreal Engine famoso pelas franquias Unreal Tournament e Bioshock; Crytek Engine conhecido pela série de jogos Crysis; Source Engine responsável por jogos de sucesso como Half- life e Portal; e o Unity mais famoso por jogos como Hearthstone e Ori and the Blind Forest. Antigamente o uso desses game engines estava restrito a grandes produtoras devido ao custo elevado de aquisição das licenças mas recentemente tem havido uma mudança na estratégia de comercialização desses produtos. O Unreal Engine 4, o Source Engine 2 e o Unity 5 agora podem ser utilizados de graça sem a necessidade de se adquirir uma licença. Você só precisará adquirir uma licença ou pagar royalties depois de ter produzido e comercializado seu jogo. O Unity Apesar de termos várias opções de engines para desenvolvimento de jogos, o Unity vem ganhando bastante destaque nesse mercado bastante concorrido. Isso acontece porque o Unity foi criado desde o início tendo em mente a sua utilização tanto por desenvolvedores quanto por artistas e designers, que geralmente produzem recursos e documentação mas não são incluídos no processo de desenvolvimento de um jogo. Isso acabou abrindo as portas para que eles pudessem contribuir mais ativamente durante o processo de desenvolvimento através de uma ferramenta integrada e expansível que permite que artistas possam realizar ajustes relativos ao visual de um jogo sem a necessidade de escrever uma linha de código sequer. O mesmo acontece com os designers que puderam auxiliar, por exemplo, no balanceamento dos jogos, na criação de novo conteúdo ou em ajustes simples das regras de jogo também sem precisar escrever código para isso. Outra característica que permitiu a entrada do Unity nesse mercado tão concorrido foi a Asset Store. A Asset Store é uma loja de recursos que podem ser adquiridos (pagos ou grátis) e utilizados na criação de jogos. Esses recursos variam desde modelos 3D, imagens e sons até scripts, ferramentas adicionais e efeitos visuais. Qualquer usuário do Unity pode compartilhar ou vender seus recursos na Asset Store criando uma fonte bastante rica de recursos para qualquer um que queira criar um jogo. Uma outra grande vantagem do Unity é a sua capacidade de publicar um mesmo jogo em mais de 20 plataformas diferentes incluindo as plataformas Android, iPhone, Playstation (3, 4, Vita), XBox (360, One), Nintendo (Wii U, 3DS), PC Desktop, PC Web, entre outras. Por todos esses motivos, escolhemos o Unity como a ferramenta mais adequada tanto para quem está começando no desenvolvimento de jogos quanto para quem já conhece um pouco do assunto e quer ganhar mais produtividade sem ter que reinventar a roda. Criando um novo projeto Antes de podermos criar um jogo no Unity precisamos baixar a última versão do engine. Para isso podemos visitar o site oficial do Unity: http://www.unity3d.com. Depois disso, basta visitar o link Get Unity e depois clicar no botão Free Download localizado ao final da coluna Personal Edition. Agora é só clicar no botão Download Installer para iniciar o download do instalador do Unity. Depois de baixado o instalador do engine, basta executar o arquivo e seguir os passos do wizard de instalação. Com isso, um ícone do Unity vai ser criado no seu Desktop. Agora para iniciar o Unity, basta dar um duplo clique nesse ícone e aguardar a inicialização. A primeira tela exibida pelo Unity será a apresentada na figura abaixo: Nesta tela o Unity mostra uma lista com os projetos mais recentes e também dois botões: Open Other prá abrir um projeto que não esteja na lista e New Project para criar um novo projeto. Para começarmos o desenvolvimento de um jogo no Unity precisamos criar um projeto então vamos utilizar o botão New Project. Depois de clicar nesse botão, o Unity mostrará a tela de criação de projetos mostrada abaixo: Nesta tela podemos dar um nome para o nosso projeto em Project Name e escolher em que pasta ficarão os seus arquivos em Location. Podemos também dizer se o nosso projeto será um jogo 2D ou 3D clicando nos textos de mesmo nome nessa janela. Depois de preenchidos os campos, podemos criar em Create Project para criar o projeto e abrir o editor do Unity pronto prá começar! O editor do Unity Sempre que abrirmos um projeto no Unity, a primeira tela que veremos será a janela do editor apresentada abaixo: É no editor que passaremos a maior parte do tempo durante a criação do nosso jogo. Por esse motivo é interessante configurar o layout do editor para que as ferramentas mais importantes fiquem sempre ao nosso alcance. O editor do Unity já possui algum padrões de layout de tela pré-configurados para o uso. Para acessá-los devemos clicar na caixa de opções de nome Layout logo acima da aba Inspector, localizada do lado direito da janela como ilustrado na figura abaixo: Apesar de já possuir algum padrões prontos, recomendamos começar com o padrão 2 by 3 e alterar a posiçãode algumas abas para facilitar o trabalho dentro do editor até alcançar a configuração mostrada abaixo: Nesta configuração, temos do lado esquerdo as abas Scene e Game, que são visualizações do nosso jogo, e do lado direito as abas Hierarchy, Project e Inspector, que serão usadas para organização, criação e configuração dos objetos e recursos do jogo. Com isso estamos prontos para começar a criação do nosso jogo! As regras do jogo: Defesa de Torres Durante o curso construiremos um jogo do início ao fim começando pela criação do projeto e adicionando aos poucos novas funcionalidades até ter um jogo completo. O jogo que criaremos será um tower defense. Neste jogo o jogador deve construir torres em posições estratégicas para criar uma linha de defesa contra inimigos invasores. Caso um inimigo consiga cruzar a linha de defesa sem ser destruído, o jogador é penalizado com a perda de uma vida. Se as vidas do jogador acabarem então é fim de jogo. O objetivo final é sobreviver o maior tempo possível à invasão. Construindo o cenário do jogo com Game Objects O que são Game Objects? No Unity, cada objeto dentro de uma cena é chamado de Game Object, por exemplo: personagens, luz, câmera, partículas... Se todos esses diferentes objetos são Game Objects, como diferenciamos uma câmera de uma partícula? Por padrão, um Game Object não tem nenhum comportamento específico. Sua diferenciação é feita a partir do conjunto de componentes (components) que escolhemos para esse objeto. Então, uma câmera é um Game Object que possui um conjunto de componentes diferentes de uma partícula, que também é um Game Object! Como todo Game Object é um objeto dentro da nossa cena, como fazemos para posicioná-lo num ponto específico do nosso ambiente? Uma componente muito importante dos Game Objects é o Transform, que permite configurarmos vários atributos interessantes, como escala (scale), rotação (rotation) e posição (position). Então, para configurarmos a posição de qualquer Game Object na nossa cena, basta acessarmos o atributo Position do componente Transform! Terreno: nosso segundo Game Object Temos nosso inimigo criado, mas ele ainda está "flutuando" na nossa cena. Para melhorar isso, vamos criar o chão do nosso jogo! Como o chão é um objeto da nossa cena, criaremos outro Game Object: o Terrain. Ao clicarmos na aba Hierarchy -> Create -> 3D Object -> Terrain, veja que esse Game Object criado possui um outro componente além do Transform, chamado Terrain: Nesse componente podemos customizar as propriedades do nosso chão, como largura, profundidade, resolução, cores... Além disso podemos criar elevações e depressões no relevo desse terreno! Manipulando a Câmera Logo ao criarmos nosso projeto no Unity, a própria ferramenta já cria uma cena com dois Game Objects: uma câmera principal e uma luz padrão. Podemos alterar seus atributos! Podemos clicar na câmera principal (Main Camera) e, na aba Inspector, manipular os seus componentes, como oTransform e Camera. No componente Camera, podemos manipular o atributo Field of View que representa o ângulo de visão da nossa câmera. Para saber mais: Field of View O olho humano possui um ângulo de visão (field of view) de aproximadamente 180 graus, porém num jogo em primeira-pessoa costumamos usar valores bem mais baixos que esse pois o cérebro humano acredita estar olhando para uma simples janela. É bastante comum encontrarmos jogos com field of view em torno de 70 graus, aumentando de acordo com a quantidade de monitores ligados até 120 graus. Movimentando o inimigo por um caminho Dando vida ao jogo Agora que já temos uma cena com alguns dos principais elementos do nosso jogo, vamos começar a adicionar algumas funcionalidades começando pelo movimentação do inimigo. Da forma como o jogo se encontra no momento, nosso inimigo poderia se movimentar em qualquer direção sem nenhum obstáculo em seu caminho. Além disso, dessa forma não ficaria claro para o jogador onde o inimigo está querendo chegar. Para melhorar isso, vamos começar adicionando detalhes ao nosso terreno para mostrar qual será o caminho a ser percorrido pelo inimigo. Para isso vamos precisar fazer modificações no objeto Chao que criamos anteriormente. Terrain e suas ferramentas O objeto Chao é um Terrain que permite a modelagem de terrenos de forma bem simples e intuitiva. Quando selecionamos um objeto do tipo Terrain, o Inspector exibe o componente Terrain que utilizaremos para desenhar o terreno do nosso jogo. Inicialmente, nesse componente temos apenas uma barra de ferramentas como na figura abaixo: A primeira ferramenta que vamos usar é a Raise / Lower Terrain correspondente ao primeiro botão da barra de ferramentas. Essa ferramenta serve para definir o relevo do nosso terreno permitindo a criação de elevações e depressões de forma bem fácil! Ao clicar nessa ferramenta, o componente irá exibir no Inspector as suas opções como ilustrado na figura: Na seção Brushes, podemos escolher o formato do pincel que usaremos para criar elevações ou depressões no terreno. Logo abaixo, temos a seção Settings onde podemos escolher o tamanho do pincel em Brush Size e a sua opacidade (força) em Opacity. Após configurar essas opções, podemos criar elevações no terreno clicando e arrastando o mouse sobre o terreno na abaScene. Para diminuir a elevação do terreno e criar depressões, basta clicar e arrastar sobre o terreno segurando a teclaSHIFT. Agora que já sabemos como usar a ferramenta, podemos criar um terreno irregular bem mais interessante que o anterior! Por exemplo, podemos modelar um terreno como o abaixo: Para delimitar o caminho, vamos utilizar mais uma ferramenta do componente Terrain. Novamente, com o Chaoselecionado, vamos agora utilizar a segunda ferramenta desse componente mostrada na figura abaixo: Essa ferramenta serve para criar regiões que possuam uma mesma altura escolhida por nós. Dessa forma, fica fácil definir áreas planas no terreno. Assim como na ferramenta anterior, temos uma seção Brush e uma seção Settings com as mesmas funcionalidades. A diferença está no funcionamento da ferramenta e na adição do atributo Height e do botão Flatten. Para utilizar a ferramenta basta clicar e arrastar sobre o terreno na aba Scene. Nesse caso, a ferramenta irá modificar a altura do terreno na região clicada para a altura definida no atributo Height. Além disso, também podemos resetar a altura do terreno inteiro de uma só vez definindo a altura em Height e clicando no botão Flatten. Então agora podemos delimitar o caminho a ser percorrido definindo Height como 0 e desenhando um caminho na abaScene começando no lado esquerdo do terreno e terminando no lado direito. Por exemplo, podemos desenhar um caminho como o exibido na figura abaixo: Como agora definimos um contraste entre o caminho e o restante do cenário, fica fácil para o jogador perceber por onde o inimigo irá passar e onde ele pretende chegar. Delimitando a área de movimento com a NavMesh Com o caminho definido, precisamos agora fazer com o que inimigo consiga se mover por este caminho sem se desviar. Mas como vamos fazer nosso inimigo saber por onde ele pode andar? Para isso utilizaremos o que chamamos no Unity deNavMesh. Uma NavMesh (abreviação de navigation mesh) é um modelo poligonal que serve para indicar por onde os objetos podem se mover. Além disso, a NavMesh permite que os objetos encontrem facilmente o menor caminho para se chegar em um determinado ponto. Para criar uma NavMesh, vamos abrir a janela Navigation indo no menu Window -> Navigation. Antes de gerar aNavMesh precisamos configurar alguns parâmetros para indicar onde nosso inimigo pode caminhar. Na janelaNavigation, vamos clicar no botão Bake para ter acesso às opções abaixo: Esses parâmetros são utilizados para dizer ao Unity como a NavMesh deve sergerada. Os atributos Agent Radius eAgent Size servem para indicar qual será o tamanho dos objetos que se moverão pela NavMesh. O atributo Max Slopeindica qual a inclinação máxima pela qual um objeto pode se mover e o atributo Step Height qual a altura máxima aceitável para um degrau que o objeto pode subir ou descer. Com os parâmetros configurados, basta clicar no botão Bake na parte inferior da janela Navigation. O Unity irá gerar aNavMesh e apresentá-la na aba Scene como na figura abaixo: A NavMesh, apresentada como uma malha na cor azul sobre o terreno, representa a área que poderá ser acessada por um objeto que se mova sobre ela. Caso a NavMesh apresente alguma região muito estreita ou não forme uma única área contínua, os objetos não conseguirão se locomover corretamente. Nesses casos, precisaremos gerar novamente aNavMesh com novos parâmetros ou depois de fazer ajustes no terreno. Transformando o inimigo em um NavMeshAgent Agora que criamos a NavMesh, como fazemos para dizer para o nosso inimigo que ele deve se mover respeitando os limites dela? A forma de fazer isso no Unity é indicando que o inimigo é um objeto que sabe interagir com uma NavMesh. Para isso, basta adicionar um componente chamado NavMeshAgent ao nosso objeto Inimigo. Na ferramenta, selecionamos o objeto Inimigo e no Inspector clicamos em Add Component -> Navigation -> Nav Mesh Agent. Agora noInspector, temos várias novas opções relacionadas ao movimento desse objeto na NavMesh como apresentado na figura abaixo: Para adequar o nosso inimigo ao NavMesh, precisamos indicar o tamanho do nosso objeto alterando seu raio em Radiuse sua altura Height em Agent Size. Além disso, alteramos também a velocidade do objeto Speed = 1 em Steering. Para finalizar, alteramos o atributo Quality = None em Obstacle Avoidance para que o inimigo possa se locomover sem se preocupar em colidir com outros objetos ou com o cenário. Fazendo o inimigo se mover com script Tendo criado a NavMesh e transformado o inimigo em um NavMeshAgent, como fazer para dizer ao inimigo para onde ele deve ir? Para fazer isso vamos ter que criar um novo comportamento para o nosso inimigo dizendo que ele deve se locomover para o fim do caminho assim que o jogo começar. Comportamentos de objetos no Unity são definidos através de scripts escritos em C#. Todo script depois de criado poderá ser usado como um componente em qualquer objeto do Unity. Para criar um novo script, podemos selecionar o objeto Inimigo e depois clicar em Inspector -> Add Component -> New Script. Depois basta preencher o campoName = Inimigo e clicar em Create and Add. Para editar o script, podemos dar um duplo clique em Project -> Inimigo. Neste momento, o Unity abrirá uma ferramenta externa para edição de scripts: o MonoDevelop. No MonoDevelop, o script Inimigo será exibido contendo o seguinte código: using UnityEngine; using System.Collections; public class Inimigo : MonoBehaviour { // Use this for initialization void Start () { } // Update is called once per frame void Update () { } } Note que o script Inimigo herda da classe MonoBehaviour para indicar que o nosso script é um comportamento do Unity e como tal poderá ser utilizado como um componente no editor. Além disso, temos os métodos Start e Update. Por enquanto, utilizaremos apenas o Start que é o método invocado automaticamente pelo Unity no momento que o objeto é criado. Vamos aproveitar esse método para ordenar que o inimigo se mova até o fim do caminho utilizando a NavMesh como guia. Mas quem é o componente responsável por fazer com que nosso objeto interaja com a NavMesh? O NavMeshAgent! Então vamos ter que pedir para o NavMeshAgent realizar essa tarefa. Para manipularmos algum componente de um objeto no script, precisamos ter uma referência para ele. Conseguimos essa referência utilizando o método GetComponent como no código abaixo: NavMeshAgent navMeshAgent = GetComponent<NavMeshAgent>(); Agora que temos uma referência para o componente NavMeshAgent do nosso Inimigo, basta invocar o métodoSetDestination neste componente como no código abaixo: public class Inimigo : MonoBehaviour { void Start () { NavMeshAgent agente = GetComponent<NavMeshAgent>(); agente.SetDestination (/*como saberemos a coordenada do fim do caminho?*/); } } Mas ainda temos um problema: o método SetDestination recebe uma posição como parâmetro indicando prá onde o objeto deve se mover. Como vamos indicar a posição do final do caminho? Para não termos que especificar as coordenadas do final do caminho na mão, podemos criar um game object vazio com o nome FimDoCaminho em nossa cena e posicioná-lo no fim do nosso caminho. Assim podemos utilizar a posição desse objeto como referência de posição para o método SetDestination! Ainda assim, precisamos de uma forma de obter uma referência para esse objeto. Nesse caso, podemos utilizar o métodoGameObject.Find passando o nome de um objeto da cena como parâmetro para receber uma referência para esse objeto: GameObject fimDoCaminho = GameObject.Find ("FimDoCaminho"); Finalmente, tendo a referência para o objeto, podemos acessar o atributo transform.position desse objeto para conseguir sua posição. Juntando tudo isso, teremos o seguinte código no script Inimigo: public class Inimigo : MonoBehaviour { void Start () { NavMeshAgent agente = GetComponent<NavMeshAgent>(); GameObject fimDoCaminho = GameObject.Find ("FimDoCaminho"); Vector3 posicaoDoFimDoCaminho = fimDoCaminho.transform.position; agente.SetDestination (posicaoDoFimDoCaminho); } } Com o script finalizado, basta rodar o jogo agora para verificar que tudo está funcionando. Vamos então clicar no botão de Play localizado na barra de ferramentas superior do editor como indicado na imagem abaixo: Quando estamos executando o jogo, o editor do Unity entra no play mode. Durante o teste do jogo, podemos manipular nossa cena criando ou removendo objetos e também alterando seus parâmetros através do Inspector. Isso é ótimo para fazer um ajuste fino de detalhes que são melhores vistos enquanto o jogo está rodando. Mas fique alerta! Todas alterações realizadas no play mode são desfeitas quando encerramos a sessão de teste clicando novamente no botãoPlay. Luz ambiente do nosso jogo Além da câmera padrão, temos uma directional light criada por padrão pronta para manipularmos no nosso jogo! Dessa vez, podemos alterar o componente Light. Além da sua cor (Color), podemos configurar a intensidade (Intensity) da luz, sua sombra (Shadow type) e até mesmo sua reflexão nos objetos (Bounce Intensity). Veja que temos quatro tipos de luz: Point: esse tipo de luz tem o máximo de iluminação no seu centro e vai escurecendo quanto mais longe estamos desse centro. É usado quando queremos simular um poste de luz, ou uma lâmpada, ou até mesmo explosões. Spot: é bem parecido com o point light, mas fica confinada a um cone de direção. Faróis de carros, luzes de palco são bons exemplos desse tipo de luz. Directional: neste tipo de luz não temos uma origem definida, nos importa somente a sua direção. Podemos simular a luz do sol ou da lua com ela. Area: em vez de criarmos vários point lights, podemos criar uma iluminação numa área. Neste caso os raios de luz serão disparados em várias direções dentro desta área. Dessa forma podemos simular uma iluminação dentro de casa de forma mais realista. Como o cálculo de iluminação é algo bastante custoso para o processador, podemos determinar se queremos uma iluminação em tempo-real ou pré-calculada pela engine alterando a opção Baking. Porém, podemos fazer essa configuração para todas as luzes da nossa cena acessando um painel específico de iluminação chamado Lighting. Nestepainel, podemos desmarcar a opção Continuous Baking. Criando o projeto Primeiramente, temos que baixar o Unity do seu site oficial e instalá-lo. Agora, podemos criar o projeto que utilizaremos durante todo o curso: 1. New Project. 2. Digite TowerDefense no campo Project Name em Create Project. 3. Com isso, a janela principal do e janela Unity Editor Update Check -a. Sugira uma correção Você não precisa submeter a resposta desse exercício. Criando o inimigo https://unity3d.com/get-unity/download?ref=personal https://www.alura.com.br/course/jogos-mobile-com-unity/section/1/exercise/1 Para criar o inimigo, vamos dar um clique com o botão direito dentro de Hierarchy e, na sequência, Create -> 3D Object -> Cube. Após a criação do nosso cubo, vamos renomeá-lo para Inimigo na aba Inspector. Ainda na aba Inspector, além de alterar o nome do nosso Game Object, vamos modificar as dimensões do Inimigo e sua posição: 1. Na aba Inspector em Transform -> Position, vamos colocar 0.1 no eixo Y. 2. Agora, em Transform -> Scale, coloque 0.2 no X, Y e Z. Onde ficou posicionado o Inimigo? Construindo o terreno Vamos criar o terreno do nosso jogo! Basta clicarmos com o botão direito em Hierarchy e Create -> 3D Object -> Terrain. Vamos aproveitar e renomear esse terreno para Chao, na aba Inspector. Cuidado para não criar o Chao dentro do Inimigo. Neste momento, nossos game objects devem ficar no mesmo nível em vez de um dentro do outro. Agora, vamos selecionar o Chao, pois precisamos alterar algumas das suas propriedades: 1. Em Inspector -> Transform -> Position vamos deixar X = -7, Y = 0 e Z = -4. 2. Na aba Inspector em Terrain, vamos selecionar o botão com uma engrenagem (o Terrain Settings). No grupo Base Terrain, selecione Custom para Material. Em Resolution, colocaremos Terrain Width = 14, Terrain Length = 8, Terrain Height = 16 eHeightmap Resolution = 129. Com o Chao criado, vamos criar uma pasta no nosso projeto. 1. É só clicar com o botão direito em Project -> Create -> Folder e renomear essa pasta para Assets. 2. Agora, vamos arrastar o Project/New Terrain para a pasta Assets. Neste momento, quantos game objects temos no nosso jogo? Configurações da câmera Ao criarmos um jogo no Unity, uma câmera padrão já é criada para nosso uso. Vamos alterar alguns parâmetros para nosso jogo ficar legal. 1. Em Hierarchy, selecione Main Camera. 2. Na aba Inspector, vamos renomeá-la para Camera. 3. Em Inspector -> Transform -> Position deixaremos X = 0, Y = 7.5 e Z = -6.6. 4. Em Inspector -> Transform -> Rotation colocaremos X = 55. Além de alterarmos o posicionamento e rotação da Camera, vamos configurar também o seu zoom. Com a Camera selecionada, vamos em Inspector -> Camera -> Field of View e colocaremos o valor 45 na barra deslizante. O que é field of view? Modelando o cenário Vamos começar modelando o terreno onde futuramente construiremos nossas defesas. Selecione o objeto Chao na aba Hierarchy. Na aba Inspector -> Terrain, selecione a ferramenta Raise / Lower Terrain: Agora, na aba Scene, modele as elevações do cenário clicando com o botão esquerdo do mouse para elevar o terreno no ponto clicado ou utilizando o comando SHIFT + botão esquerdo do mouse para baixar o terreno no ponto clicado. Cuidado para não gerar um terreno muito acidentado ou plano demais. Seu cenário deve ficar parecido com o da figura abaixo: Sugira uma correção Você não precisa submeter a resposta desse exercício. Construindo o caminho Agora que já temos o terreno modelado, vamos construir o caminho que será percorrido pelos inimigos. Na aba Inspector -> Terrain, selecione a ferramenta Paint height: Digite o valor 0 em Inspector -> Terrain -> Height. Na aba Scene, clique e arraste com o botão esquerdo do mouse sobre o terreno para desenhar o caminho. Seu caminho deve ficar parecido com a figura abaixo: https://www.alura.com.br/course/jogos-mobile-com-unity/section/1/exercise/5 Aproveite o espaço do terreno para desenhar seu caminho mas lembre- se de que deve sobrar espaço suficiente para que o jogador construa as torres futuramente. Além disso, procure não fazer um caminho estreito demais, caso contrário os inimigos não conseguirão se mover com a velocidade necessária. Sugira uma correção Você não precisa submeter a resposta desse exercício. Criando o Navigation Mesh Para que os inimigos possam se mover apenas pelo caminho, precisamos gerar uma NavMesh que será um modelo auxiliar de navegação que será produzido a partir do nosso terreno. 1. Vá em Menu -> Window -> Navigation para abrir a aba Navigation onde configuraremos a geração daNavMesh. 2. Em Navigation -> Bake -> Baked Agent Size, vamos colocar Agent Radius = 0.15 e Max Slope = 0 e então clicar no botão Bake. 3. Verifique se a NavMesh gerada e apresentada na aba Scene representa um caminho contínuo sem interrupções como o mostrado na figura abaixo. Caso isso não aconteça, selecione novamente o objeto Chao em Hierarchy e utilize as ferramentas de terreno para alargar o caminho nos locais necessários e repita o passo anterior. https://www.alura.com.br/course/jogos-mobile-com-unity/section/1/exercise/6 O que aconteceria se criássemos nosso navmesh com um Max Slope maior que zero? Alinhamento do inimigo no caminho Vamos preparar o nosso Inimigo para que ele consiga se mover pelo caminho. Para isso, vamos posicioná-lo no início do caminho e orientá-lo na direção do caminho. 1. Selecione o objeto Inimigo na aba Hierarchy e posicione-o no início do caminho. Para isso utilize a ferramenta apresentada na figura abaixo. Para não corrermos o risco de alterarmos a posição Y do inimigo ao movê-lo, podemos movimentá-lo clicando e arrastando sobre as setas vermelha e azul. Desse modo, o movimento fica restrito ao eixo selecionado. 1. Agora rotacione o Inimigo para que seu eixo Z aponte na direção do caminho. Para isso, selecione o Inimigo e no Inspector -> Transform clique e arraste sobre o eixo Y para rotacionar até que o eixo Z aponte para a direção correta. Movimentando o inimigo (parte 1) Precisamos agora fazer com que nosso Inimigo se mova. Primeiramente, vamos adicionar um novo comportamento a ele para que ele possa utilizar a NavMesh como referência para se locomover. 1. Com o Inimigo selecionado, clique no botão Add Component e selecione Navigation -> Nav Mesh Agent. 2. Em Inspector -> Nav Mesh Agent -> Agent Size, coloque Radius = 0.1 e Height = 0.2. Esses parâmetros representam o raio e a altura do nosso Inimigo que serão considerados quando este estiver se locomovendo pela NavMesh. Movimentando o inimigo (parte 2) Agora sim, precisamos criar e associar um novo script ao nosso Inimigo que ficará responsável por pedir que oInimigo se movimente da posição atual até o final do caminho quando o nosso jogo for iniciado. Com o Inimigo selecionado, na aba Inspector, clique em Add Component, selecione New Script, preencha Name = Inimigo e clique em Create and Add. Veja que agora temos um script Inimigo e um objeto Inimigo, ficou mais difícil para nos localizarmos no nosso projeto. Vamos arrumar isso? 1. Clique com o botão direito em Project -> Create -> Folder e renomeie a pasta para Script. 2. Arraste nosso script Inimigo para a pasta Script. Movimentando o inimigo (parte 3) Agora, vamos manipular nosso script recém criado. 1. Na aba Project, dê um duplo clique em Inimigo (dentro de Script). 2. A ferramenta externa MonoDevelop para edição dos scripts será aberta. Vamos digitar o seguinte código nesta ferramenta: public class Inimigo : MonoBehaviour { void Start () { NavMeshAgent agente = GetComponent<NavMeshAgent>(); agente.SetDestination (/*como saberemos a coordenadado fim do caminho?*/); } } 1. Precisamos criar um objeto no fim do caminho para utilizar como referência no nosso script. Clique com o botão direito dentro da aba Hierarchy e selecione Create Empty. Em seguida, renomeie o objeto criado paraFimDoCaminho. 2. Selecione o objeto FimDoCaminho e em Inspector -> Transform clique no botão com a engrenagem (Settings) e selecione Reset Position. 3. Agora reposicione o FimDoCaminho de modo que ele esteja no final do caminho mas continue dentro da NavMesh. 4. De volta ao nosso script, agora podemos indicar o FimDoCaminho como destino para nosso objeto: public class Inimigo : MonoBehaviour { void Start () { NavMeshAgent agente = GetComponent<NavMeshAgent>(); GameObject fimDoCaminho = GameObject.Find ("FimDoCaminho"); Vector3 posicaoDoFimDoCaminho = fimDoCaminho.transform.position; agente.SetDestination (posicaoDoFimDoCaminho); } } 1. Para testar nosso jogo e verificar que o Inimigo se move como esperamos, clique no botão Play localizado no centro da barra superior do editor como indicado na figura. Para encerrar o teste, clique novamente no mesmo botão. O que aconteceu com nosso inimigo? Refinando o movimento Dependendo de como o caminho foi construído, quantidade de curvas e espaço disponível, o Inimigo pode apresentar algumas dificuldades para se locomover. Vamos ajustar alguns parâmetros para tornar o movimento mais natural. 1. Em Inspector -> Steering coloque Speed = 1 e em Inspector -> Obstacle Avoidance coloqueQuality = None. 2. Teste estas mudanças de parâmetros clicando novamente no botão Play. 3. Com o jogo rodando, selecione o Inimigo no Hierarchy e altere o parâmetro Speed em Inspector -> Steering. Perceba que podemos visualizar em tempo real a mudança de velocidade do Inimigo. 4. Modifique o valor do parâmetro para qualquer coisa diferente de 1 e em seguida clique em Play para encerrar a execução do jogo. O que aconteceu com o valor do parâmetro Speed? Manipulando a luz ambiente Da mesma forma que o Unity cria uma câmera padrão, para que possamos ver nossa cena já é criada uma luz padrão chamada Directional Light! Precisaremos fazer apenas algumas alterações nela. Em Hierarchy, selecione Directional Light. Na aba Inspector, alteraremos seu nome para Sol. Ainda com o nosso objeto selecionado, em Inspector -> Transform selecione o botão com uma engrenagem e, na sequência, Reset Position. Com a posição zerada, em Inspector -> Transform -> Position, vamos deixar Y = 10. Agora, podemos alterar sua rotação! Em Inspector -> Transform -> Rotation vamos colocar X = 35, Y = 45 e Z = 0. Por fim, vamos alterar a intensidade da luz do Sol. Vamos em Inspector -> Light e colocar Intensity = 0.65. Por enquanto, vamos evitar que nossas luzes sejam recalculadas durante o jogo. Para isso, precisaremos abrir um menu novo chamado Lighting. Vá em Window -> Lighting e com o botão Scene selecionado, vá ao final dessa tela e desmarque a opçãoContinuous Baking. Construindo a torre e disparando mísseis Começando a batalha: nossa primeira torre Até o momento fizemos um cenário, manipulamos a câmera, luz e temos até um inimigo percorrendo um caminho, mas ainda falta o principal: a Torre! Durante a criação de um jogo, existem vários profissionais envolvidos de diversas áreas: programadores, designers, animadores, desenhistas, engenheiros de som, roteiristas... O modelo 3D de uma torre geralmente passa pelos desenhistas, animadores e designers para que os programadores possam incorporá-la ao jogo. Será que os programadores tem que esperar todo esse processo terminar para só então começar a trabalhar com a torre? Para evitar essa ociosidade e diminuir o tempo de criação de um jogo, enquanto o modelo da torre é feito, os programadores simulam o modelo com formas 3D mais simples, mas com as mesmas características da torre original! Dessa forma, quando o modelo 3D da torre estiver pronto, é só trocar a forma simples pelo modelo final. Isso é bastante fácil de fazer no Unity. Então, vamos começar a programar nossa torre, mesmo sem ter o modelo 3D pronto neste momento, para vermos como funciona esse fluxo. Como será essa torre? Nossa torre será composta por dois "cubos": um será o corpo da torre e o outro será o canhão da torre arranjados dessa forma: Como essa torre será um objeto do nosso jogo, lembre-se que ela será também um Game Object! Então, podemos criar as partes da nossa torre diretamente na aba Hierarchy. Agora temos um CorpoDaTorre e um CanhaoDaTorre, mas em nenhum lugar temos um objeto chamado, de fato,Torre. Caso queiramos mover todas as partes da torre, precisaríamos selecionar uma a uma, e se nosso objeto for composto por 20 partes diferentes? Para gerenciarmos melhor nossos objetos e transformá-los num único corpo, podemos criar um outro Game Object cuja única função será agrupar as diversas partes do nosso objeto composto. Mas qual Game Object serviria para essa função? Podemos criar mais um Cube, ou uma Sphere...? Veja que temos a possibilidade de criar um Empty Game Object, que pode ser usado justamente para agrupar outros objetos! Com esse Empty Game Object criado, podemos simplesmente colocar as partes da torre em seu interior e chamá-lo deTorre. Dessa forma, podemos arrastar e fazer qualquer configuração à torre como um todo, sem ter que repetir cada configuração para todas as suas partes. Míssil Criamos nossa torre, mas até o momento ela não faz nada; nossos inimigos podem andar tranquilamente na sua frente que nada acontecerá. Para mudar essa situação, vamos tornar nossa Torre um pouco mais perigosa fazendo-a disparar mísseis! Vamos trabalhar com um objeto simples que representará nosso míssil: um Cube. A diferença dessa vez está na seguinte pergunta: quem será o responsável por criar esse Game Object? O jogador já verá criados todos os mísseis que nossa torre pode disparar? Como nossa Torre será a responsável por disparar o Missil, temos que torná-lo um objeto instanciado dinamicamente no nosso jogo: um prefab. Prefab em detalhes Imagine que tenhamos um Game Object criado para cada Missil a ser disparado pela nossa Torre, mas agora queremos alterar o tamanho desses mísseis. Como os Game Objects são independentes dos outros (mesmo que tenham o mesmo nome), se quisermos alterar um atributo de um Game Object, teríamos que replicar essa alteração em todos os outrosGame Objects da nossa cena. Para não precisarmos fazer esse trabalho, o Unity possui um tipo de objeto chamado Prefab que permite armazenarmos um Game Object juntamente com todos os seus componentes e atributos. Então, quando precisarmos criar um Game Object a partir de um prefab, basta instanciar esse prefab usando o método: Instantiate (meuPrefab); Uma grande vantagem do uso de prefabs é que qualquer alteração feita num prefab é refletida para todos os objetos na cena instanciados a partir dele! Disparando mísseis Precisamos definir o comportamento do responsável em disparar mísseis, mas quem seria esse responsável? A própriaTorre! Então, vamos criar um script C# chamado Torre que conterá o comportamento da nossa torre. Como todo script que define o comportamento de um Game Object, esse deverá também ser filho de MonoBehaviour: public class Torre : MonoBehaviour { } Logo que o objeto Torre aparecer na cena, vamos fazê-lo disparar um Missil. Então, vamos usar o próprio métodoStart do MonoBehaviour para instanciar nosso prefab: public GameObject prefabDoMissil; void Start() { Instantiate (prefabDoMissil); } Neste momento, como o nosso script Torre sabe quem é o prefab que representa o prefabDoMissil? Ele simplesmente não sabe! Precisamos, agora, vincular nosso prefab a esse atributo. Usaremos a própria interface do Unitypara isso: Tornando o ponto de disparo relativo à torre Ao chamar o método Instantiate passando um prefab, por padrão o Game Object instanciado é posicionado de acordo com o atributo position do prefab. Ao mover a Torre, como não alteramos a posição do nosso prefab, o Missilcontinuou sendo disparado da posição anterior. Mas sempre teremos que lembrar de posicionar o Missil perto da Torre? E se tivermos mais de uma Torre, onde oprefab do Missil ficará posicionado? Para resolvermos isso, vamos criar na nossa Torre um ponto de disparo, que usaremos como origem dos mísseis instanciados por essa Torre. Dessa forma, cada Missil disparado por uma Torre saberá em qual ponto deverá nascer. Como o ponto de disparo não é nada além de um ponto, podemos criar um Empty Game Object para representá-lo. Além disso, como o ponto de disparo pertence ao CanhaoDaTorre, podemos explicitar isso colocando o ponto dentro do canhão na hierarquia: Como usaremos esse ponto no Instantiate? Agora, podemos usar uma versão mais "inteligente" do método Instantiate, que recebe a posição onde o prefab será instanciado e uma rotação inicial também: Instantiate (prefab, posicao, rotacao); Mas, para saber a posicao, precisamos encontrar nosso PontoDeDisparo. Para isso, podemos usar o método Findpassando o nome do Game Object desejado e, na sequência, obter sua posição: GameObject pontoDeDisparo = this.transform.Find ("CanhaoDaTorre/PontoDeDisparo").gameObject; Vector3 posicao = pontoDeDisparo.transform.position; Por fim, podemos atribuir uma rotação que o objeto instanciado terá. Podemos usar a própria rotação contida no prefabchamando transform.rotation: GameObject pontoDeDisparo = this.transform.Find ("CanhaoDaTorre/PontoDeDisparo").gameObject; Vector3 posicaoDoPontoDeDisparo = pontoDeDisparo.transform.position; Instantiate (projetilPrefab, posicaoDoPontoDeDisparo, transform.rotation); Com isso, somos capazes de disparar mísseis sempre na mesma posição relativa à torre. Implementando a movimentação do míssil Conseguimos disparar o Missil, mas ainda ele fica "congelado" na frente do canhão da nossa torre. Agora, vamos fazê-lo se movimentar pelo campo de batalha. Como faremos para nosso Missil se movimentar? Estamos falando de um comportamento que somente nosso Missilterá, então, precisaremos de um script associado ao nosso Game Object! Vamos criar um script C# chamado Missil, que deverá ser filho de MonoBehaviour para garantir que esse comportamento será aplicado ao nosso objeto do jogo: public class Missil : MonoBehaviour { void Start () { } void Update () { } } Por padrão, o Unity já popula nossa classe com os métodos Start e Update. Já vimos antes que o Start é chamado sempre que nosso objeto for criado, mas e o método Update? O método Update Todo MonoBehaviour possui o método Update, que é chamado pelo Unity a cada frame do jogo. Essa característica doUpdate permite implementarmos atualizações de qualquer tipo em um Game Object com a garantia de que será efetuada a cada frame do nosso jogo! Para mover o Missil, precisaremos reposicionar nosso objeto a cada frame do jogo, então esse será um uso perfeito para o método Update. Mas como será feita essa alteração de posição? void Update () { Vector3 posicaoAtual = transform.position; //Como alteraremos essa posicaoAtual? } Podemos fazer nosso objeto se mover para a frente com uma velocidade constante de 10 metros. Por convenção, o eixo Zde qualquer game object é tratado como a sua "frente". Basta capturarmos o eixo Z e seremos capazes de mover nosso objeto para a frente. Para facilitar, todo GameObject já possui uma forma de obtermos especificamente o eixo Z. Basta fazer: Vector3 frente = transform.forward; Agora é só usar essa frente e multiplicar pela nossa velocidade para sabermos o quanto temos que mover nosso objeto: private float velocidade = 10; void Update () { Vector3 posicaoAtual = transform.position; Vector3 frente = transform.forward; Vector3 deslocamento = frente * velocidade; } Agora, podemos adicionar esse deslocamento à posicaoAtual do nosso GameObject: private float velocidade = 10; void Update () { Vector3 posicaoAtual = transform.position; Vector3 frente = transform.forward; Vector3 deslocamento = frente * velocidade; transform.position = posicaoAtual + deslocamento; } Então, a cada frame movemos nosso GameObject em 10 metros. Será que isso é suficiente para garantir um deslocamento constante? Frames não são constantes Um frame nada mais é do que a exibição de um único instante do jogo. Porém, para esse frame ser exibido, muitos cálculos devem ser efetuados pela GPU, e esses cálculos não possuem um tempo fixo para serem encerrados; dessa forma, um frame pode demorar mais do que outro para ser exibido! No nosso caso, como estamos deslocando o GameObject com uma velocidade de 10, na realidade estamos dizendo que queremos alterar nosso objeto com uma velocidade de 10 metros por frame em vez de 10 metros por segundo. Para corrigir isso, o próprio Unity já oferece uma forma de compensar esse tempo de cálculo de cada frame: a classeTime. Usando Time para compensar o frame Usando o atributo deltaTime da classe Time, podemos capturar quanto tempo demorou entre a exibição do frame atual e o anterior. E essa informação pode ser usada para tornar nossa velocidade independente da taxa de frames exibidos: private float velocidade = 10; void Update () { Vector3 posicaoAtual = transform.position; Vector3 frente = transform.forward; Vector3 deslocamento = frente * velocidade * Time.deltaTime; transform.position = posicaoAtual + deslocamento; } Disparando vários mísseis Em vez de disparar apenas um único Missil, podemos fazer nossa Torre disparar mísseis em um intervalo fixo. Então, olhando o script da nossa Torre, já podemos fazer uma alteração logo de cara: colocar o comportamento de disparo logo no Update! public class Torre : MonoBehaviour { void Update () { Atira (); } } Mas agora, a cada quanto tempo nossa Torre atira? Sempre que o método Update for chamado, o que é bastante rápido! Para podermos parametrizar o tempo do disparo, vamos contar com o Time.time, que permite obtermos o tempo desde o início do jogo: private float momentoDoUltimoDisparo; public float tempoDeRecarga = 1f; private void Atira () { float tempoAtual = Time.time; if (tempoAtual > momentoDoUltimoDisparo + tempoDeRecarga) { momentoDoUltimoDisparo = tempoAtual; // Dispara... } } Então, sempre que estiver passado um intervalo tempoDeRecarga entre o tempoAtual e o momentoDoUltimoDisparo, podemos disparar um míssil! Mísseis teleguiados Conseguimos disparar mísseis num intervalo fixo, porém nossos mísseis sempre caminham para a frente. Desse jeito munca seremos capazes de acertar algum inimigo. Vamos fazer algo melhor: logo após serem disparados, nossos mísseis irão seguir o inimigo! Como nosso Missil sempre anda para a frente, podemos a cada frame corrigir a sua rota para sempre apontá-lo para a posição atual do inimigo. Isso será feito num método chamado AlteraDirecao no próprio script do Missil: void Update () { Anda (); AlteraDirecao (); } Nesse método AlteraDirecao precisamos fazer nosso Missil apontar para o alvo. Se ele sempre estiver apontando, ao caminhar para a frente, cada vez estará mais perto do alvo, dando a ideia de ser "teleguiado". Mas como faremos o Missil sempre apontar para o alvo? Podemos pegar o vetor que representa a direção atual do míssil e corrigí-lo para apontar para a direção do inimigo. Como ambos são vetores, podemos subtraí-los: Ao fazer essa subtração, o vetor resultanterepresenta a nova direção do míssil, justamente a "correção" que estávamos procurando! private void AlteraDirecao() { Vector3 direcaoDoMissil = transform.position; Vector3 direcaoDoInimigo = alvo.transform.position; Vector3 novaDirecao = direcaoDoInimigo - direcaoDoMissil; } Temos a nova direção. Só precisamos fazer o Missil rotacionar para essa novaDirecao. Podemos fazer isso usando o método LookRotation da classe Quaternion atribuindo isso para a rotation do próprio GameObject: transform.rotation = Quaternion.LookRotation (novaDirecao); Construindo a primeira torre Vamos criar um novo objeto no nosso jogo: a torre! Como nossa torre será composta por cubos, vamos criar um novo3D Object clicando com o botão direito dentro de Hierarchy e 3D Object -> Cube. Com o Cube selecionado no Hierarchy, vamos alterar seu nome para CorpoDaTorre lá na aba Inspector. Vamos também alterar sua posição. Em Inspector -> Transform -> Position vamos colocar X = 0, Y = 0.65 e Z = 0 Por fim, vamos redimensionar esse cubo. Em Inspector -> Transform -> Scale X = 0.5, Y = 1.3 e Z = 0.5 Podemos melhorar nossa torre. Em vez de termos somente o CorpoDaTorre, vamos criar também um outro cubo que será o CanhaoDaTorre! Crie um novo objeto 3D clicando com o botão direito em Hierarchy -> 3D Object -> Cube e o renomeie paraCanhaoDaTorre. Em Inspector -> Transform -> Position, deixe X = 0, Y = 1.55, Z = 0 Agora, em Inspector -> Transform -> Scale, vamos colocar X = 0.25, Y = 0.25, Z = 1 Pronto! O que será que acontecerá se movermos apenas o CanhaoDaTorre pelo nosso jogo? Agrupando game objects Temos dois objetos independentes, mas na realidade esses objetos são partes de um objeto em comum, a nossa torre. Vamos dizer isso para o Unity. Clique com o botão direito em Hierarchy -> Create Empty. Vamos renomear esse objeto para Torre. Com a Torre selecionada, em Inspector -> Transform -> Position, deixe X = 0, Y = 0, Z = 0 Agora, é só arrastar os objetos CorpoDaTorre e CanhaoDaTorre para dentro do objeto Torre! Dessa forma, temos um objeto composto por vários outros objetos. Na tela do jogo, o que acontece ao arrastar o objeto Torre? Criação do míssil da torre Neste momento, nosso míssil será um outro cubo. Então, vamos construí-lo? Clique com o botão direito em Hierarchy -> 3D Object -> Cube. Não esqueça de renomear esse objeto para Missil. Selecione o Missil na aba Scene e arraste-o para a frente do canhão da nossa Torre. Vamos diminuir um pouco o tamanho desse Missil em Inspector - > Transform -> Scale com os valores X = 0.2, Y = 0.2 e Z = 0.4 Agora, vamos organizar a estrutura de pastas do nosso jogo para que nossos elementos não fiquem bagunçados! Clique com o botão direito em Project -> Create -> Folder e renomeie essa pasta para Prefabs. Agora, é só arrastar nosso Missil para dentro dessa pasta Prefabs. Veja que, neste momento, temos dois Missil: um deles na aba Hierarchy e outro dentro de Project/Prefabs. Como nossos mísseis deverão ser criados pela Torre, podemos deletar o Missil da aba Hierarchy! Criando o script da torre Para dar vida à nossa Torre, precisaremos de um script C#. Vamos selecionar nossa Torre na aba Project. Agora, vamos em Inspector -> Add Component -> New Script e chame-o de Torre. Arraste esse script para dentro da pasta Scripts. Vamos abrir esse script. Dê um clique duplo em Project/Scripts/Torre. Com o script aberto, remova o método Update. No fim, nosso script Torre, deverá estar assim: public class Torre : MonoBehaviour { void Start() { } } Quando esse método Start é chamado? Nossa torre pode disparar mísseis Agora, vamos fazer nossa Torre disparar um Missil! No nosso script Project/Scripts/Torre, vamos criar o método Atira que instanciará um novoProject/Prefabs/Missil: public class Torre : MonoBehaviour { public GameObject projetilPrefab; private void Atira () { Instantiate (projetilPrefab); } } Ainda não estamos chamando esse método Atira em nenhum lugar. Vamos chamá-lo dentro do Start da nossaTorre: public class Torre : MonoBehaviour { void Start () { Atira (); } } Indicando o game object que deve ser disparado Nossa Torre ainda não pode atirar, pois não dissemos qual é o GameObject que representa nosso Missil. Para resolver isso, vamos selecionar Hierarchy/Torre. Agora, é só arrastar Project/Prefabs/Missil para Inspector -> Torre -> Projetil Prefab. Clique em Play e veja que nossa Torre atirará mísseis! Agora, movimente nossa Torre. De onde os mísseis são disparados agora? Aprimorando o disparo da Torre Ao mover a Torre, o disparo continua saindo do ponto anteriormente definido e não acompanha a nova posição doCanhaoDaTorre. Vamos, agora, deixar o ponto de disparo vinculado ao CanhaoDaTorre. Em Hierarchy, vamos selecionar o CanhaoDaTorre com o botão direito e Create Empty. Agora, renomeie paraPontoDeDisparo. Vamos alterar a posição do PontoDeDisparo indo em Inspector -> Transform -> Position e colocando X = 0, Y = -0.1 e Z = 0.8. Com isso, nosso PontoDeDisparo deverá ficar na frente do CanhaoDaTorre. Para disparar nosso Missil a partir do PontoDeDisparo, precisaremos alterar nosso script da Torre que está emProject/Scripts/Torre. Como é o método Atira o responsável por disparar nosso Missil, vamos modificá-lo: public class Torre : MonoBehaviour { private void Atira () { GameObject pontoDeDisparo = this.transform.Find ("CanhaoDaTorre/PontoDeDisparo").gameObject; Vector3 posicaoDoPontoDeDisparo = pontoDeDisparo.transform.position; Instantiate (projetilPrefab, posicaoDoPontoDeDisparo, Quaternion.identity); } } Movimentando o Missil pelo campo Conseguimos disparar nosso Missil, porém ele ainda não pode se mover pelo campo. Para dar vida ao nosso objeto, precisaremos de um script! Selecione Project/Prefabs/Missil e Inspector -> Add Component -> New Script. Chame esse script de Missil e clique em Create and Add. Vamos abrir esse script e alterar o seu método Update: public class Missil : MonoBehaviour { private float velocidade = 10; void Update () { Anda (); } private void Anda () { Vector3 posicaoAtual = transform.position; Vector3 deslocamento = transform.forward * Time.deltaTime * velocidade; transform.position = posicaoAtual + deslocamento; } } DIsparando vários mísseis (parte 1) Em vez de disparar apenas um Missil, vamos fazer nossa Torre disparar vários deles. Para isso, vamos chamar o método Atira no Update do nosso script que está em Project/Scripts/Torre. Como apagamos esse método Update logo na criação do script Torre, podemos criá-lo agora: public class Torre : MonoBehaviour { void Start () { // Podemos remover a chamada ao método Atira que estava aqui... } void Update () { Atira (); } } Clique em Play e veja nossa Torre disparando vários mísseis! DIsparando vários mísseis (parte 2) Para definirmos um intervalo de disparo, precisamos novamente alterar nosso script Project/Scripts/Torre. Vamos contar o tempo entre um disparo e outro e, somente quando ultrapassar o tempoDeRecarga da Torre, dispararemos um novo Missil: public class Torre : MonoBehaviour { private float momentoDoUltimoDisparo; [Range(0,3)] public float tempoDeRecarga = 1f; private void Atira () { float tempoAtual = Time.time; if (tempoAtual > momentoDoUltimoDisparo + tempoDeRecarga) { momentoDoUltimoDisparo = tempoAtual; // Código que já tínhamos nesse método... } } } Clique em Play e veja os mísseis sendo disparados a cada segundo.Veja que colocamos [Range(0,3)] logo acima do tempoDeRecarga. Com o jogo rodando, clique no gameObject da Torre e olhe sua aba Inspector. Como o Tempo De Recarga é exibido? Mísseis teleguiados Vamos fazer nosso Missil perseguir o Inimigo. Para isso, vamos alterar nosso script emProject/Scripts/Missil para que o Missil ande e altere sua direção a cada intervalo de tempo. Vamos chamar um método que ainda não existe chamado AlteraDirecao: public class Missil : MonoBehaviour { [Range(0,5)] public float velocidade; void Update () { Anda (); AlteraDirecao (); } } No nosso método AlteraDirecao precisaremos rotacionar nosso Missil para a direção do alvo. Usando a classeQuaternion: public class Missil : MonoBehaviour { private void AlteraDirecao() { Vector3 posicaoAtual = transform.position; // Quem é esse alvo? Vector3 posicaoDoAlvo = alvo.transform.position; Vector3 direcaoDoAlvo = posicaoDoAlvo - posicaoAtual; transform.rotation = Quaternion.LookRotation (direcaoDoAlvo); } } Nosso código já está quase pronto, mas como pegaremos esse alvo? Precisaremos trazer para nosso script o objeto Inimigo que está na cena do jogo. Então, logo ao instanciarmos esseMissil teremos que buscar esse Inimigo. Basta usarmos a o método Find da classe GameObject: public class Missil : MonoBehaviour { //... private GameObject alvo; void Start () { alvo = GameObject.Find("Inimigo"); } //Métodos anteriores... } Clique em Play e veja nossos mísseis perseguirem o Inimigo! Porém, o que acontece quando o Missil atinge oInimigo? Detectando colisões e destruindo inimigos Detectando colisões com Colliders Já temos uma torre capaz de disparar mísseis mas o que deve acontecer quando um míssil atinge um inimigo? A primeira coisa que precisamos fazer é detectar o momento em que ocorre uma colisão entre o míssil e o inimigo. Quando trabalhamos com modelos 3D mais detalhados é interessante aproximar a forma do modelo utilizando formas simples como cubos e esferas que permitem um cálculo mais eficiente das colisões entre todos objetos de uma cena. Por exemplo, veja o modelo abaixo: Note que o modelo é bastante detalhado e poderíamos até utilizá-lo para verificar as colisões mas isso teria um custo muito elevado de processamento no nosso jogo. Mesmo que estivéssemos fazendo uma simulação de corrida ultra-realista, não precisamos de um modelo tão detalhado para checar as colisões. Poderíamos tratar cada pneu como um cilindro de poucas faces e o corpo do carro como um ou mais cubos. Se o nosso jogo fosse ainda mais simples, como nos antigos jogos de corrida de arcade, poderíamos até mesmo tratar o carro inteiro como um único cubo como na figura! Como o cálculo das colisões é realizado a cada novo quadro de nosso jogo, ganhamos um tempo precioso simplificando essa tarefa. No Unity, esse modelo simplificado utilizado para o cálculo das colisões é chamado de collider. Para adicionar um collider a um objeto existente, só temos que selecionar o objeto e no Inspector, clicar em Add component -> Physics e escolher um dos vários colliders disponíveis. Depois basta configurar os parâmetros do colliderde forma a ajustá-lo à forma do modelo do seu game object. Note que os game objects do Unity que representam as formas primitivas já possuem os seus respectivos colliders adicionados. Como estamos interessados em detectar a colisão entre o míssil e o inimigo, e como esses dois objetos foram criados usando formas primitivas então não vamos precisar adicionar os colliders manualmente. Então agora só falta especificar o que devemos fazer quando uma colisão entre esses objetos ocorre. Primeiro vamos dizer para o Unity que estamos interessados em sermos avisados quando uma colisão ocorre. Fazemos isso selecionando o nosso Missil, por exemplo, e no Inspector -> Box Collider marcamos a opção Is Trigger. Com isso estamos dizendo que a área desse collider funcionará como um gatilho que irá disparar um método do script do Missil quando o objeto colidir com outro objeto que possua um collider. Agora precisamos editar o script associado ao Missil e implementar o método OnTriggerEnter que será chamado quando ocorrer a colisão. Neste método vamos aproveitar e destruir o Missil invocando o método Destroy passando o próprio game object do míssil como parâmetro: public class Missil : MonoBehaviour { // Códigos anteriores void OnTriggerEnter (Collider elementoColidido) { Destroy (this.gameObject); } } Tendo especificado o que fazer em caso de uma colisão do míssil, o que acontece quando testamos o jogo e um míssil atinge um inimigo? Nada! O que será que aconteceu? Para descobrir o que está acontecendo, precisamos entender um pouco melhor como o Unity lida com colisões. Simulando física e colisões com Rigidbodies No Unity, a detecção de colisão entre dois ou mais objetos é responsabilidade do simulador de física. Como nenhum objeto do Unity tem seus movimentos sujeitos às leis da física por padrão, então faz sentido que nossos objetos não sejam considerados na detecção de colisões. Então precisamos dizer ao Unity para tratar nossos objetos como objetos físicos! Para que isso aconteça é necessário dizer que um game object possui o comportamento de um Rigidbody. Um Rigidbody terá seus movimentos controlados pelo simulador de física do Unity, levando em conta fatores como sua massa, gravidade, entre outros. Sabendo disso, podemos fazer com que nosso Missil se comporte como um Rigidbody selecionando-o e clicando emInspector -> Add Component -> Physics -> Rigidbody. Mas se testarmos nosso jogo agora veremos que nossos mísseis caem imediatamente após terem sido disparados! Isso acontece porque agora ele está sofrendo a ação da gravidade! Vamos então corrigir isso definindo que queremos que ele seja tratado como um objeto físico mas não queremos que ele fique sujeito a ação da gravidade. Podemos definir isso indo no Inspector -> Rigidbody e desmarcando a opção Use Gravity. Agora sim nossos mísseis voltaram a ter o comportamento de antes! Testando o jogo agora, veremos que a colisão é detectada e o míssil é destruído como esperávamos mas o que acontece se um míssil atingir sem querer o cenário? Para chegarmos numa resposta, vamos selecionar o nosso objeto Chao e verificar os componentes desse objeto noInspector. Temos os componentes Transform, Terrain e Terrain Collider! Como esse objeto também tem umcollider, o míssil também será destruído se colidir com o terreno! Esse tipo de comportamento poderia tornar o nosso jogo mais realista mas dependendo da irregularidade do terreno, isso pode tornar o jogo difícil demais para o jogador. Nesse caso, seria interessante que o míssil ignorasse as colisões com o terreno... mas como poderíamos fazer isso? Categorizando nossos Game Objects com Tags Se prestarmos atenção no nosso método OnTriggerEnter do script Inimigo, veremos que esse método recebe como parâmetro um Collider. Esse Collider representa o collider do outro objeto que colidiu com o nosso. Então, nessa hora poderíamos perguntar para esse Collider quem é o objeto associado a ele! Então basta verificar se o nome do objeto com o qual colidimos é "Inimigo". Mas se implementarmos a verificação dessa forma, o que acontece se resolvermos alterar o nome do nosso inimigo? Teríamos que verificar todos os nossos scripts em buscas de referências ao nome antigo e substituí-lo pelo novo nome. No Unity existe uma maneira melhor de fazer isso. Ao invés de comparar os objetos utilizando seus nomes, podemos associar um objeto com uma Tag. Uma Tag é como um rótulo que podemos colocar em nossos objetos para agrupá-los. Assim podemosverificar se um objeto possui ou não uma determinada Tag para realizar alguma ação. Para criar uma nova Tag, selecionamos um objeto qualquer e no Inspector clicamos na caixa de seleção ao lado da palavra Tag indicada na figura abaixo: Depois selecionamos a opção Add Tag.... Com isso, abriremos uma nova tela no Inspector com opções referentes aos recursos de Tags e Layers. Em Inspector -> Tags, temos a lista de todas as tags que foram criadas no nosso projeto. Inicialmente, esta lista estará vazia e podemos criar uma nova tag clicando no botão + localizado no canto inferior esquerdo da lista como na figura abaixo: Com isso adicionamos uma nova entrada Tag 0 à lista de tags. Podemos alterar o nome dessa tag para algo que faça mais sentido no nosso caso, por exemplo, "Inimigo". Agora já temos uma nova tag definida mas como associamos essa tag com o nosso inimigo? Para isso, vamos selecionar novamente o Inimigo e no Inspector selecionar Tag = Inimigo. Pronto! Nosso Inimigo agora pertence à categoria "Inimigo". De volta ao script Inimigo, precisamos verificar se o objeto associado ao collider possui a tag "Inimigo". Para isso, utilizamos o método CompareTag do game object passando como parâmetro o nome da tag que queremos verificar: public class Missil : MonoBehaviour { // Códigos anteriores void OnTriggerEnter (Collider elementoColidido) { if (elementoColidido.CompareTag ("Inimigo")) { Destroy (this.gameObject); } } } Se testarmos o jogo agora, veremos que o Missil só é destruído quando colide com o Inimigo. O único problema é que por enquanto nosso inimigo está invencível! Usando atributos para balancear o jogo Podemos fazer com que o inimigo seja destruído assim que for atingido por um míssil mas isso tornaria o jogo fácil demais. Ao invés disso, vamos adicionar um atributo no nosso inimigo para marcar a quantidade de pontos de vida que ele possui. Em seguida, vamos fazer com que nossos mísseis causem uma certa quantidade de pontos de dano ao atingir o inimigo. Quando o inimigo não tiver mais pontos de vida, então podemos destruí-lo. Começamos modificando o script Inimigo para adicionar um novo atributo. Para que possamos alterá-lo no editor do Unity, vamos tornar esse atributo público: public class Inimigo : MonoBehaviour { public int vida; } Isso funciona mas acabamos permitindo que qualquer objeto do nosso jogo também tenha acesso ao atributo vida doInimigo. Podemos alterar o modificador de acesso do nosso atributo para private mas o que acontece se fizermos isso? O editor do Unity também não terá mais acesso a esse atributo! Para manter o nosso atributo privado e ainda assim permitir que o editor tenha acesso a ele, podemos utilizar a anotação[SerializeField]. Dessa forma, podemos escrever nosso código assim: public class Inimigo : MonoBehaviour { [SerializeField] private int vida; } Utilizando essa mesma ideia, podemos alterar o script do Missil para indicar a quantidade de pontos de dano que ele causa: public class Missil : MonoBehaviour { [SerializeField] private int pontosDeDano; } Agora precisamos fazer com que o míssil reduza os pontos de vida do inimigo ao atingí- lo. Qual dos nossos scripts é responsável por agir quando uma colisão acontece? O script Inimigo! Vamos alterar então o nosso métodoOnTriggerEnter para adicionar esse novo comportamento: public class Missil : MonoBehaviour { void OnTriggerEnter (Collider elementoColidido) { if (elementoColidido.CompareTag ("Inimigo")) { Destroy (this.gameObject); Inimigo inimigo = elementoColidido.GetComponent<Inimigo>(); // aqui pedimos para o inimigo reduzir seus pontos de vida } } } No script acima, pedimos uma referência para o componente Inimigo do elementoColidido. Mas agora precisamos de alguma forma de alterar a quantidade de pontos de vida do inimigo. Como o atributo vida é privado, vamos ter que fornecer um método para deduzir uma certa quantidade de pontos de vida do inimigo: public class Inimigo : MonoBehaviour { public void RecebeDano(int pontosDeDano) { vida -= pontosDeDano; if (vida <= 0) { Destroy(this.gameObject); } } } Note que nesse script já aproveitamos e destruímos o inimigo caso seus pontos de vida cheguem a 0. Agora que temos esse script, basta invocá-lo no script do Missil: public class Missil : MonoBehaviour { void OnTriggerEnter (Collider elementoColidido) { if (elementoColidido.CompareTag ("Inimigo")) { Destroy (this.gameObject); Inimigo inimigo = elementoColidido.GetComponent<Inimigo>(); inimigo.RecebeDano(pontosDeDano); } } } Antes de testar o jogo, não podemos esquecer de definir os valores iniciais para os atributos vida e pontos de dano. Fazemos isso selecionando o objeto Inimigo e modificando o valor Inspector -> Inimigo -> Vida para 10. A mesma coisa para o Missil alterando o valor de Inspector -> Missil -> Pontos de Dano para 2. Para certificar que tudo funciona como o esperado, podemos testar o jogo novamente verificando se agora os inimigos resistem ao impacto de alguns mísseis antes de serem destruídos. Detectando colisões Primeiro vamos fazer com que o Missil disparado pela torre seja destruído quando colidir com algum outro objeto. Na aba Project, selecione o Missil e em Inspector -> Box Collider marque a opção Is Trigger. Com isso dizemos para o Unity que a colisão deste objeto será utilizada como um gatilho para executar algum script. Ainda no Inspector, clique no botão Add Component, selecione Physics -> Rigidbody e no componenteRigidbody que foi adicionado desmarque a opção Use Gravity pois não queremos que nosso míssil seja afetado pela gravidade. Agora adicione um novo método no nosso script Missil para lidar com a colisão: public class Missil : MonoBehaviour { // Códigos anteriores void OnTriggerEnter (Collider elementoColidido) { Destroy (this.gameObject); } } Teste o jogo clicando no botão Play. O que acontece agora quando o Missil colide com algum outro objeto? Diferenciando colisões entre diferentes objetos Nosso Missil está sendo destruído sempre que colide com o cenário ou com um inimigo. Como nosso cenário tem muitas elevações e os inimigos se movem bastante, o Missil tem muita dificuldade em atingir o inimigo sem colidir com o terreno. Vamos modificar um pouco comportamento do Missil de modo que ele só seja destruído quando atingir um inimigo. Primeiro precisamos ter alguma forma de distinguir entre o cenário e um inimigo. Para isso utilizaremos o recurso de Tags do Unity. Selecione o Inimigo e em Inspector -> Tag selecione Add Tag.... Agora clique no botão + em Inspector -> Tags e preencha Tag 0 = Inimigo. Selecione novamente o Inimigo na aba Hierarchy e em Inspector -> Tag selecione Inimigo. Agora que o objeto Inimigo possui uma etiqueta Inimigo associada a ele, utilize essa nova informação para distinguir o objeto com o qual o Missil colidiu no método OnTriggerEnter: public class Missil : MonoBehaviour { // Códigos anteriores void OnTriggerEnter (Collider elementoColidido) { if (elementoColidido.CompareTag ("Inimigo")) { Destroy (this.gameObject); } } } Destruindo o inimigo ao ser atingido Como o objetivo do Missil é destruir os inimigos, vamos fazer com que quando o Missil atinja um Inimigo, ambos sejam destruídos. Para isso altere o script Missil: public class Missil : MonoBehaviour { // Códigos anteriores void OnTriggerEnter (Collider elementoColidido) {if (elementoColidido.CompareTag ("Inimigo")) { Destroy (this.gameObject); Destroy (elementoColidido.gameObject); } } } Teste o jogo novamente e veja que agora o inimigo é destruído quando é atingido pelo míssil. Mas o que acontece com os demais mísseis que tinham aquele inimigo como alvo? Destruindo os mísseis após um tempo fixo Vamos fazer com que os mísseis tenham um tempo de vida fixo e que eles se autodestruam quando esse tempo acabar. Modifique o script Missil para definir esse novo comportamento: public class Missil : MonoBehaviour { void Start () { alvo = GameObject.Find("Inimigo"); AutoDestroiDepoisDeSegundos (10); } private void AutoDestroiDepoisDeSegundos(float segundos) { Destroy (this.gameObject, segundos); } } Além disso, altere também o método Update do Missil para que ele não altere a direção do míssil se o alvo não existir mais: public class Missil : MonoBehaviour { // Códigos anteriores void Update () { Anda (); if (alvo != null) { AlteraDirecao (); } } } Agora teste novamente o jogo e observe o comportamento dos mísseis depois do inimigo ser destruído. Aumentando a resistência dos inimigos Nosso jogo agora ficou fácil demais já que um míssil é suficiente para destruir o inimigo. Vamos aumentar a resistência do inimigo atribuindo a ele pontos de vida e fazendo com que os mísseis sejam capazes de causar um número fixo depontos de dano. Quando os pontos de vida do inimigo chegarem a 0 então ele é destruído. Adicione um atributo para marcar os pontos de vida no script Inimigo: public class Inimigo : MonoBehaviour { [SerializeField] private int vida; } Analogamente, adicione agora um atributo para marcar os pontos de dano causados pelo Missil: public class Missil : MonoBehaviour { [SerializeField] private int pontosDeDano; } Agora que temos esses atributos, podemos definir os pontos de vida iniciais do Inimigo e os pontos de dano doMissil pelo Inspector. Selecione o Inimigo na aba Hierarchy e em Inspector -> Inimigo -> Vidacoloque 10. Depois, selecione o Missil na aba Project e em Inspector -> Missil -> Pontos De Danocoloque 2. Agora modifique o comportamento da colisão do Missil para que ele reduza os pontos de vida do Inimigoconforme a quantidade de pontos de dano que o Missil causa: public class Missil : MonoBehaviour { void OnTriggerEnter (Collider elementoColidido) { if (elementoColidido.CompareTag ("Inimigo")) { Destroy (this.gameObject); Inimigo inimigo = elementoColidido.GetComponent<Inimigo>(); inimigo.RecebeDano(pontosDeDano); } } } Modifique também o script do Inimigo para incluir um método que reduza uma certa quantidade de pontos de vida e destrua o Inimigo caso esses pontos cheguem a 0: public class Inimigo : MonoBehaviour { public void RecebeDano(int pontosDeDano) { vida -= pontosDeDano; if (vida <= 0) { Destroy(this.gameObject); } } } Agora teste o jogo e note quantos mísseis são necessários para destruir um inimigo. Múltiplas torres para combater infinitos inimigos Gerando muitos inimigos Um único inimigo já não é mais suficiente para manter nosso jogo interessante já que ele pode ser destruído facilmente por nossa torre. Vamos melhorar isso gerando mais inimigos! Para começar, precisaremos de um objeto que ficará responsável por instanciar os novos inimigos. Como esse objeto não precisa ser visível para o jogador, que tipo de objeto iremos criar? Criaremos um game object vazio e associaremos a ele um script gerador de inimigos. Uma forma prática de criar um objeto vazio no Hierarchy é utilizar o atalho CMD+SHIFT+N (Mac) ou CTRL+SHIFT+N(Windows). Depois só precisamos renomeá-lo para algo que faça mais sentido como PontoGeradorDeInimigos. Como iremos gerar novos inimigos na posição desse objeto, vamos posicioná-lo no começo do nosso caminho tomando o cuidado de mantê- lo sobre o terreno (coordenada Y = 0). No script que associaremos ao PontoGeradorDeInimigos, vamos precisar instanciar novos Inimigo mas antes disso, o que precisamos fazer com o Inimigo para que isso seja possível? O que o método Instantiate recebia como um parâmetro? Um prefab! Então o que precisamos fazer é transformar o Inimigo em um prefab. Para fazê-lo basta arrastar o Hierarchy/Inimigopara Project/Prefabs lembrando de remover o Hierarchy/Inimigo logo em seguida. Com o prefab preparado, vamos agora adicionar um script GeradorDeInimigos ao objeto PontoGeradorDeInimigos. Esse script deverá instanciar novos inimigos em intervalos regulares. Onde já fizemos isso antes? Nossa torre também faz algo bastante similar, ela instancia novos mísseis em intervalos regulares! Logo vamos ter um script bem parecido para o nosso gerador de inimigos: public class GeradorDeInimigos : MonoBehaviour { [SerializeField] private GameObject inimigo; private float momentoDaUltimaGeracao; [Range(0,3)] [SerializeField] private float tempoDeCriacao = 2f; void Update () { GeraInimigo (); } private void GeraInimigo () { float tempoAtual = Time.time; if (tempoAtual > momentoDaUltimaGeracao + tempoDeCriacao) { momentoDaUltimaGeracao = tempoAtual; Vector3 posicaoDoGerador = this.transform.position; Instantiate (inimigo, posicaoDoGerador, Quaternion.identity); } } } Para que tudo funcione bem, só falta vincular o prefab do Inimigo ao nosso objeto PontoGeradorDeInimigos. Então selecionamos o PontoGeradorDeInimigos e arrastamos Project/Prefabs/Inimigos para Inspector -> Gerador De Inimigos -> Inimigo. Pronto! Já temos nosso gerador de inimigos em funcionamento! Escolhendo em quem atirar Até esse momento, nossa torre não precisava se preocupar em como escolher seu alvo já que tínhamos apenas um inimigo. Agora que temos infinitos inimigos o que fazer? Outro problema é que nossa torre hoje tem alcance infinito! Isso significa que a sua posição não faz diferença alguma no nosso jogo. Precisamos fazer com que mais prá frente, o posicionamento de uma torre se torne uma decisão importante para o jogador. Um jeito de conseguir isso é fazendo com que a torre tenha um raio de alcance limitado. Dessa forma, ela só poderia atirar nos inimigos que estivessem mais próximos. Podemos então adicionar um atributo raioDeAlcance ao script da Torre: public class Torre : MonoBehaviour { [SerializeField] private float raioDeAlcance; } Mas se agora a torre tem um raio de alcance, nem todos os inimigos do cenário são alvos válidos já que eles podem estar longe demais. Para levar isso em conta, vamos começar criando um método EscolheAlvo que inicialmente ficará responsável por pegar apenas os inimigos que estão na área de alcance da torre. Uma forma de fazer isso é utilizando o método estático FindGameObjectsWithTag do game object. Esse método devolve todos os objetos da cena que possuem uma tag específica. Para nossa sorte, já temos uma tag que distingue os inimigos dos demais objetos! Vamos então fazer essas alterações no script Torre: public class Torre : MonoBehaviour { private Inimigo EscolheAlvo() { GameObject[] inimigos = GameObject.FindGameObjectsWithTag("Inimigo"); return null; } } Com isso temos todos os inimigos da cena armazenados no array inimigos. Como fazemos agora para verificar se estes inimigos se encontram ao alcance da torre? Basta calcular a distância de cada um deles até a nossa torre e comparar com o raio de
Compartilhar