Ponto V!

Home Unity Programando Gameplay - Parte #1
Bruno Xavier
Programando Gameplay - Parte #1Imprimir
Escrito por Bruno Xavier

Olá novamente caro leitor!

Hoje volto com mais um texto especialmente escrito para os amigos do PontoV, falando sobre uma das poucas coisas que gostamos mais do que jogar games: Criar a jogabilidade de nossos próprios jogos.

Se existe uma faceta que define o que é um vídeo-game e porque gostamos tanto deles, isso podemos chamar de “gameplay” ou simplesmente “jogabilidade”.

Nada irá definir mais a identidade de um jogo do que sua forma de lidar com a interação entre jogador e programa; nada em um game pode proporcionar mais prazer ou decepção do que sua jogabilidade e lhe garanto que este é o principal fator que levou a maioria dos desenvolvedores a trabalharem com jogos eletrônicos ao invés de simplesmente joga-los. Qual gamer nunca teve aquele momento em um de seus jogos preferidos onde notou que algo ficaria melhor se determinado elemento fosse apresentado de uma forma diferente e desejou ter poder de mudar aquilo naquele exato instante?!

Eu acredito que as pessoas que constantemente passam por essas situações acabam se tornando os mais obstinados em se tornar um Game Designer, exatamente para suprir essa necessidade de ser capaz de moldar a experiência da maneira que acredita que ela deve ser.

Sendo assim, nesta série de artigos vamos examinar todos os elementos possíveis que afetam diretamente o desenvolvimento da jogabilidade de qualquer game. Começando por:

RAYCASTS

“Raycasting” é de certa forma um algoritmo matemático inicialmente utilizado para solucionar problemas na rasterização de pixels, de objetos em gráficos 3D para imagens 2D, muito semelhante ao “Raytracing”, que ainda hoje é muito utilizado na indústria de animação.

A significante diferença entre raycasting e raytracing é apenas a quantidade de raios que são enviados por frame e o uso final dos dados retornados por cada raio.

Raytracing é um termo utilizado para descrever a técnica de enviar um raio para cada pixel na tela do ponto de vista da câmera e tratar a cor de cada um destes pixels de acordo com as diversas informações retornadas por cada um destes raios. Em geral, a quantidade de dados retornada por cada raio em raytracing é tão grande que computar todas estas informações para cada frame a uma taxa de quadros que mantenha a ilusão de movimento na tela se torna praticamente impossível; Sendo assim cada frame é pré-computado e posteriormente editado para dar forma aos filmes em 3D que vemos hoje... Apesar de não ser possível para computadores desta geração trabalhar raytracing em tempo real, existem diversos estudos que tornam o uso desta tecnologia em games apenas uma questão de tempo, como demonstrado por pesquisadores da NVIDIA.

Raycasting é basicamente o mesmo processo. A diferença primária aqui é o tipo de informação retornada pelo raio e a quantidade de raios utilizados no frame. É importante ter esta relação em mente pois sempre que utilizar raycasts, você deve ter em mente que eles podem destruir a fluidez dos frames na tela se forem utilizados de forma incorreta ou em grande quantidade.

Em Unity raycasts são utilizados apenas para retornar informações de colisões com corpos físicos, colliders da engine física “PhyX”.

Utilidade de um Raycast

Hoje em dia, sendo muito difícil imaginar um game sem simulação de física, é quase impossível produzir jogabilidade sem utilizar raycasting. Praticamente todos os jogos modernos utilizam esta ferramenta de uma forma ou de outra.

Seja para detectar a existência de chão abaixo do personagem, para ativar triggers, para medir distância entre objetos, etc... Raycasting está sempre lá.

Veja um exemplo de jogabilidade dispondo de raycasting como um de seus pilares principais: https://vimeo.com/47868127

Neste exemplo toda a interatividade do jogador com os objetos no cenário é determinada por um único raycasting que viaja do centro da câmera até 2 metros a frente do personagem. De acordo com as informações retornadas pelo raio, cada tipo de objeto disponibiliza uma interação específica com o jogador, ativar botões, pegar o objeto, etc. O raio é literalmente a visão do jogador e é o amago de todas as suas ações neste jogo.

Raycasts em Unity

Se já examinou a documentação da Unity sobre raycasting ou já os utilizou antes em algum projeto deve já saber como é simples lidar com esta API. No entanto, existem pequenos detalhes que a documentação não explica e que você talvez deva tomar nota:

  1. Cuidado com a quantidade de raios que você utiliza em cada Update(). Quanto mais limitada for a plataforma alvo de seu jogo e maior o número de raios que seu jogo utilizar ao mesmo tempo, menos FPS você terá na cena.
  2. Sempre que possível utilize Raycasts em Layers especificas, isso reduz drasticamente a quantidade de informações que um raio pode retornar, tornando a execução mais leve.

Exemplo de um raycast para layers especificas:

using UnityEngine;
using System.Collections;

public class SampleTrigger : MonoBehaviour {
    public float rayCastDistance = 200f;

    //
    private int Objects = 1 << 23;
    private int Interactives = 1 << 24;
    private RaycastHit Hit;

    //
    void Update() {

        Ray ray = new Ray(transform.position, transform.forward);

        if (Physics.Raycast(ray,out Hit,rayCastDistance,(Objects | Interactives))) {
            Debug.Log(Hit.gameObject.name);
        }
    }
}

Note como a bitmask para cara layer é declarada, “1 << 23;” e “1 << 24;”.

E como elas são utilizadas em conjunto para isolar o raycast nas devidas camadas que nos interessa, “(Objects | Interactives)”.

Definindo uma bitmask desta forma, nosso raycast está colhendo informações somente destas duas layers, no caso 23 e 24, e ignorando todas as outras 30 layers que estão disponíveis na Unity, tornando o processo final muito mais rápido; No caso do raio colidir com algum objeto ele não retorna valor algum se o objeto não pertencer a alguma destas camadas. Conforme seu projeto cresce, estas pequenas otimizações são o que fazem a diferença a longo prazo.

Acessando Components via Raycasts

É bastante comum a necessidade de acessar componentes quando um raycast retorna um objeto. Fazer isso é bem simples, basta armazenar um ponteiro para o componente, “GetComponent<T>()”, e pode-se modifica-lo como bem entende:

Void Update() {

    Ray ray = new Ray(transform.position, transform.forward);

    if (Physics.Raycast(ray,out Hit,rayCastDistance,(Objects | Interactives))) {

        Renderer myRenderer = Hit. gameObject.GetComponent<Renderer>();

        myRenderer.enabled = false;

        // ou

        Hit. gameObject.GetComponent<Renderer>().enabled = true;
    }
}

Source-Code

Segue para estudo o script que controla a interatividade do jogador com os objetos do jogo usado como referência no vídeo acima. Além de identificar que tipo de objeto o jogador está olhando a sua frente, o script lida com outros pormenores como assinar um shader especifico que destaca objetos interativos, etc. Posto com intuito de incentivar discussão sobre o assunto, qualquer dúvida ou comentário não se esqueça de compartilhar conosco; Ai vai:

using UnityEngine;
using System.Collections;

public class HighLightTrigger : MonoBehaviour {

    public float rayCastDistance = 120f;
    public Shader highlightShader;
    public AudioClip Interact;
    public AudioClip Grab;
    public Transform Hand;
    public Animation Actor;

    //
    private FPP Controller;

    private HighLight Highlight;

    private GameObject Viewing;

    private Shader HeldShader;

    private int Objects = 1 << 23;
    private int DimObjects = 1 << 25;
    private int Interactives = 1 << 24;

    private int LastLayer;

    private RaycastHit Hit;

    private bool Cast;

    //

    [System.NonSerialized] public GameObject Held;

    private bool CanHold = true;

    //
    void Awake() {

        Controller = transform.root.GetComponentInChildren<FPP>();
    }

    //
    void LateUpdate() {
        InvokeRepeating("Look",0,1.0f);
    }

    //
    void Look() {
        if (Viewing != null) {
            if (Viewing.tag == "Active" && Viewing.layer != 25) {
                HighLight hs = Viewing.GetComponent<HighLight>();
                hs.SwitchShader(HeldShader);
            }
        }

        //
        Viewing = LookingAt();

        //
        if (Viewing != null) {
            if (Viewing.tag == "Active" && Viewing.layer != 25) {
                HighLight hs = Viewing.GetComponent<HighLight>();

                HeldShader = hs.SwitchShader(highlightShader);
            }
        }
    }

    //
    void Hold() {CanHold = false;}
    void Unhold() {if (Held == null) {CanHold = true;}}

    //
    //
    GameObject LookingAt() {
        Ray ray = new Ray(transform.position, transform.forward);

        if (Physics.Raycast(ray,out Hit,rayCastDistance,(Objects|DimObjects|Interactives))) {
            if(Hit.transform.gameObject.tag == "Active") {
                Hit.transform.gameObject.SendMessage("ShowIcon",SendMessageOptions.RequireReceiver);

                if (Controller.Active) {

                    if ((Hit.transform.gameObject.layer == 23 || Hit.transform.gameObject.layer == 25) && cInput.GetKeyDown("Grab") && CanHold) {
                        if (!audio.isPlaying) {
                            audio.clip = Grab; 
                            audio.Play();
                        }

                        if (Hit.transform.parent != Hand) {
                            Invoke("Hold",0.25f);
    
                            Held = Hit.transform.gameObject;
                            Highlight = Held.GetComponent<HighLight>();

                            LastLayer = Held.layer;

                            Cast = Held.renderer.castShadows;

                            Held.layer = 20;
                            Held.renderer.castShadows = false;
                            Held.transform.parent = Hand;
                            Held.transform.localPosition = new Vector3(0,(Hit.transform.localScale.y/2)+Highlight.HandOffset,0);
                            Held.transform.localRotation = new Quaternion(0,0,0,0);

                            try {
                                Held.rigidbody.isKinematic = true;
                            }
                            catch{}

                            //Controller.gameObject.BroadcastMessage("ActingGrab",SendMessageOptions.RequireReceiver);

                            Actor.Stop(); 
                            Actor.Play("Hold");
                        }
                    }
    
                    if (Hit.transform.gameObject.layer == 24 && cInput.GetKeyDown("Interact")) {
                        if (!audio.isPlaying && Actor.IsPlaying("Idle")) {
    
                            Actor.Stop(); 
                            Actor.Play("Interact"); 
                            Actor.PlayQueued("Idle");
    
                            // if Glove Phaser > 0: Actor.Play("Glove Interact"); Actor.PlayQueued("Glove Idle");
    
                            StartCoroutine_Auto(Interaction(Hit.transform.gameObject));
                        }
                    }
                }
    
                return Hit.transform.gameObject;
            } else {
                return null;
            }
        } else if (Controller.Active) {
            if (cInput.GetKeyDown("Grab") && Held != null && !CanHold) {
    
                try {
                    Held.rigidbody.isKinematic = false;
                }
                catch{}
    
                StartCoroutine_Auto(ResetLayer(Held,LastLayer));
    
                Actor.Stop(); 
                Actor.Play("Drop"); 
                Actor.PlayQueued("Idle");
    
                Held.transform.parent = null; 
                Held.renderer.castShadows = Cast;
    
                Held = null; 
                Highlight = null; 
                Cast = false; 
        
                return Held;
    
            } else {
                Invoke("Unhold",0.5f); 
                return null;
            }
        } else {
            return null;
    }


    //
    //
    IEnumerator ResetLayer(GameObject OBJ, int Layer) {
        yield return new WaitForSeconds(0.5f); 
        OBJ.layer = Layer;
    }

    //
    IEnumerator Interaction(GameObject OBJ) {
        yield return new WaitForSeconds(0.5f);

        audio.clip = Interact; audio.Play();

        OBJ.SendMessage("Activate",SendMessageOptions.RequireReceiver);

    }

}

E isso é tudo por enquanto. Estou aberto a sugestões sobre qualquer outro fator que considera crucial para a programação de jogabilidade e que gostaria de saber mais a respeito.

Espero que a leitura lhe tenha sido útil, feliz programação e boa sorte em seus projetos!


Comentários (7)
  • janrie carllos tidres  - Sugestão
    avatar

    Bom cara, como você pediu sugestões, uma sugestão minha seria falar um pouco sobre a fisica para jogos, por exemplo transformações vetorias e tudo que evolva a fisica, simples e complexa, dos jogos.

  • Vinícius Godoy de Mendonça
    avatar

    Oi Juan.

    Nós temos no site uma sessão somente para matemática e física dos jogos: http://www.pontov.com.br/site/arquitetura/54-matematica-e-fisica

    É claro, podemos considerar incluir lá artigos falando especificamente da engine de física da Unity, mas ela encapsula toda a complexidade dos cálculos.

  • Bruno Xavier
    avatar

    Sem problemas Janrie; próximo texto será sobre física na Unity :)
    Assim que analisar quais são as dúvidas mais frequentes do pessoal que está iniciando, envio o texto. Abraço!

  • Diego  - Sugestãp
    avatar

    Interessante, como sugestão eu deixaria o parallax scrolling.

  • Bruno Ribeiro de Melo  - Seguestão
    avatar

    Muito bacana seus textos, estou lendo aos poucos cada um deles.

    Algo que gostaria de ver é como criar GUI com uma interface boa.

  • Luiz  - Como fazer sistema de fases no unity
    avatar

    vc poderia colocar do começo ao fim de como fazer o script onde devemos colocar o resto como as telas de onde as pessoas vao jogar vc coloca simplesmente um plano vazio so muda o nome

Escrever um comentário
Your Contact Details:
Gravatar enabled
Comentário:
[b] [i] [u] [url] [quote] [code] [img]   
:angry::0:confused::cheer:B):evil::silly::dry::lol::kiss::D:pinch::(:shock:
:X:side::):P:unsure::woohoo::huh::whistle:;):S:!::?::idea::arrow:
Security
Por favor coloque o código anti-spam que você lê na imagem.
LAST_UPDATED2  

Busca

Linguagens

Twitter