ZIM - Lasers
Autor: Márcio Freitas | Publicado: 2012-04-28T17:16:00-07:00




Depois do último post com os detalhes sobre o jogo ZIM, algumas pessoas me pediram pra comentar um pouco sobre como foi feita os lasers das sentinelas.


No início, pensei que seria mais simples se objetos "refletores" tivessem um script pra detectar que foram atingidos pelo laser e emitir um laser na direção/ângulo apropriada. Apesar de funcionar, achei que uma solução mais limpa seria o emissor script emissor do laser, se responsabilizar pelas reflexões.

Para explicar o código dos lasers preciso explicar que neste projeto&nbsp;tenho enumerações globais (Enums) para as&nbsp;tags&nbsp;e&nbsp;layers&nbsp;configurados na Unity3D. Por que ?
Primeiramente, facilita para as pessoas que pegam o projeto, saberem que tags e layers precisam definir na Unity. Segundo, pois algumas vezes, precisamos dizer pra um script que objetos com a tag X deve ser afetados mas com a tag Y ou no layer Z, devem ser ignorados. Permitir que um script afete apenas um grupo de tags ou layers direto pelo editor, da uma flexibilidade imensa pro projeto. Também diminui a quantidade de strings fixas no código, que é um anti-padrão que gera bastante rertabalho caso uma tag seja renomeada mais a frente.

Então, nos códigos abaixo, quando vir "GameTags" e "GameLayers" saiba que se trata destas enumerações globais contendo todas as tags e layers do projeto. Um efeito colateral é ter que converter string para enumeração e vice-versa.

O laser é basicamente um LineRenderer então, nada mais justo que tornar o LineNereder um componente necessário pro script
 [RequireComponent (typeof(LineRenderer))]  


Abaixo 4 variáveis importantes que definem respectivamente:


quanto dano o lase causa por segundo
objeto atualmente sob a mira do laser
tags que recebem dano em contato com o laser
tags de objetos que refletem o laser

    public float damagePerSecond = 30; 

private GameObject currentTarget;
public List AffectedTags = new List();  
    public List ReflectiveTags = new List();  





Perceba que tenho listas de tags como parâmetros públicos, assim posso definir diretamente no editor, que objetos refletem o laser ou&nbsp;recebem dano&nbsp;desta sentinela.



O LineRenderer permite ter um número variável de vértices. Uma linha reta sem refletir em nada, teria 2 vértices. A cada reflexão do laser, teremos 1 vértice a mais. Então precisei de uma variável para controlar a quantidade de vértices do LineRenderer
   private int vertexCount = 1;  



A cada frame, defino o LineRenderer como tendo apenas 1 vértice posicionado na origem do gameObject e em seguida chama o método&nbsp;ApplyLaserReflection()
   void FixedUpdate()  
   {  
     // Reseta o line renderer e faz o primeiro ponto iniciar na posicao deste transform  
     vertexCount=1;  
     lineRenderer.SetVertexCount(vertexCount);  
     lineRenderer.SetPosition(0,transform.position);  
     // Emite o laser e calcula as possíveis reflexoes  
      ApplyLaserReflection(transform.position,transform.forward);  
   }  


Este é o méodo principal responsável por emitir o laser.

Observe que este método checa se a tag objeto na mira está entre as tags que devem receber dano (configurada pelo editor) ou está entre as tags que refletem o laser. A cada reflexão do laser um novo vértice é adicionado ao LineRenderer e há uma nova recursão do método. Para evitar loops infinitos caso um objeto esteja 100% alinhado com o laser, impus um limite de 10 reflexões. Caso o objeto atingido não seja um objeto reflexivo, o laser para na posição deste objeto.

 void ApplyLaserReflection(Vector3 position, Vector3 direction)  
   {      
     RaycastHit hitInfo;  
     vertexCount++;  
     lineRenderer.SetVertexCount(vertexCount);  
     if (Physics.Raycast(position, direction, out hitInfo, Mathf.Infinity) &amp;&amp; vertexCount &lt; 10)  
     {  
       // Adiciona um novo vertice ao lineRederer  
       lineRenderer.SetPosition(vertexCount-1, hitInfo.point);  
       // Obtem a tag do objeto que atingimos  
       GameTags targetTag = (GameTags)Enum.Parse(typeof(GameTags), hitInfo.transform.tag);  
       // Atingimos alguma coisa que reflete o laser ?  
       if ( ReflectiveTags.Contains(targetTag))  
       {  
         Vector3 newDirection = Vector3.Reflect(direction, hitInfo.transform.forward.normalized);  
         //testamos por mais reflexoes...  
         ApplyLaserReflection(hitInfo.point, newDirection);  
       }  
       else   
       {  
         //Atingimos algo que o laser pode causar dano ?  
         if (AffectedTags.Contains(targetTag))  
           currentTarget = hitInfo.transform.gameObject;  
         else  
           currentTarget = null;  
       }  
     }else  
     {  
       lineRenderer.SetPosition(vertexCount-1 ,position + (direction * 100));  
     }      
   }  


Como este método atualiza a variável global&nbsp;currentTarget&nbsp;o método Update() aplica o dano enviando uma mensagem com BroadcastMessage();

Bem simples e eficaz!