segunda-feira, 16 de setembro de 2013

Unity C# - Delegates e Eventos

E ae pessoal, beleza?

Como havia dito ontem, vamos começar a efetivamente tentar fazer posts diários sobre o andamento dos nossos projetos, decisões que tomamos, problemas que encontramos, etc, mas eu tinha escrito esse post a tanto tempo que tava escrito "acho que até o final de maio vamos ter muita coisa pra postar aqui..." hauhauha to me sentindo praticamente jogando Sim City no Cheetah Speed, negócio ta tenso... mas, enfim, esse post é dedicado a programação novamente, sobre um tópico que aprendi recentemente e tem me ajudado muito na produção dos nossos jogos, algo que é EXTREMAMENTE ÚTIL, Delegates e Eventos!

Esse é um tutorial talvez um pouquinho mais avançado que o de Singletons, mas de verdade, é bem simples, vou tentar explicar de um jeito bem fácil de entender. Outra coisa que acho válido mencionar logo de cara é que eu sou noob de delegates e eventos e eu ainda sinto que não estou usando isso a todo o poder... por isso, fique a vontade nos comentários pra me chamar de retardado. (Também é C#, pra Unity)

-Delegates e Eventos

De acordo com o google images, pra manter o padrão...

Impressionantemente, uma imagem que realmente descreve o que é um delegate com eventos!
Tendo em mente que eu não sou nenhum mestre dos delegates, eu diria que Delegates e Eventos podem fazer juntos de maneira simplória: Em uma classe, você cria um delegate (praticamente como um método, você da um nome, um tipo de retorno e quantos parâmetros esse método recebe), e um evento que é do tipo do delegate (isso é, um evento que recebe os mesmos parâmetros e retorna a mesma coisa que o delegate criado). Agora, todas as classes do seu projeto podem se "inscrever" no seu evento, e serem avisados que aquele evento foi chamado pra fazerem seja lá o que eles quiserem fazer.

Ok, não foi a melhor explicação do mundo, mas calma, eu explico melhor mais pra baixo.

Bom, mesmo sendo noob dos delegates, eu já descobri alguns usos que são simplesmente fantásticos. Um exemplo que eu acho muito útil é para controlar Inputs (principalmente mobile, mas é útil de toda forma). Vamos supor você tem um jogo de matar monstrinho, e na tela existem 5 botões referentes a suas habilidades pra matar monstrinho. Muito provavelmente você tem uma classe que é responsável por controlar os inputs (vou chamar aqui de InputHandler), e nessa classe você diz que quando um dedo toca a tela, o jogo deve lançar um Raycast da câmera pra dentro do jogo, descobrir se acertou algum botão, e SE acertou, soltar a magia. Maravilha.

No entanto, sem delegates e eventos, a sua classe InputHandler está diretamente atrelada a sua classe SkillHandler, que gerencia o uso de todas as magias. Obrigatóriamente você precisa de uma referencia ao SkillHandler dentro do InputHandler, e quando um dedo bate em um botão, você chama algo tipo SkillHandler.UseSpell(2) onde 2 é o index do botão tocado.

Maravilha, funciona... mas e se agora você decidiu fazer uma tela de stats de jogo, então você quer marcar quantas vezes cada habilidade foi usada? E se agora você quiser que toda vez que o jogador use a magia X, uma luz mude de cor? E se agora você quer que comece a chover sempre que o cara use uma magia de água? Vai socar todas essas checagens no InputHandler? Vai deixar uma classe ser totalmente dependente da outra pra que tudo isso funcione direito?

Esse é certamente um dos inúmeros casos em que os delegates salvam o dia. O InputHandler deve ser responsável pelo Input e nada mais. As outras classes precisam simplesmente receber o input e tratar da maneira correta para saber o que fazer com aquele input. Sem delegates, seria necessário criar referência de todas as classes que possam precisar de input no inputhandler, para fazer chamadas diretas de métodos, ou pelo menos pra enviar mensagens para esses objetos. Mas com delegates... isso é tudo diferente, e na verdade, muito mais simples.

Bom, a primeira coisa a se fazer nesse caso é efetivamente criar um delegate na classe InputHandler, e atrelar um evento a este delegate. Isso é feito da seguinte forma:



Agora, para facilitar ainda mais as coisas, podemos considerar a classe InputHandler um singleton, afinal de contas nós queremos que ela centralize todos os inputs recebidos e apenas de as ordens para as outras classes, você jamais precisará de duas instancias dessa classe em uma cena. Para isso, como conversado neste post, vamos adicionar as seguintes linhas pra classe InputHandler:


E finalmente, para acabar a classe InputHandler, vamos adicionar uma chamada ao evento que foi criado anteriormente. Essa chamada deve acontecer no momento em que o botão da skill é pressionado, como por exemplo no código:


Agora, vamos trabalhar na classe SkillHandler. Ela deve efetivamente soltar a magia quando o botão for pressionado. O código aqui é bem simples, e a classe ficaria assim:


Mas o que estamos fazendo aqui? Bom, no OnEnable, isso é, quando essa instancia ficar ativa, nós estamos registrando essa instancia da classe no evento OnSpellUse, da classe InputHandler, e dizendo na hora do registro que o método a ser chamado é o método UseSpell(). Na prática, o que isso quer dizer é que sempre que o InputHandler chamar o evento onSpellUse(); como demonstrado um pouco acima, o método UseSpell() da classe SkillHandler será chamado automaticamente. Na prática, o InputHandler não sabe absolutamente nada sobre a classe SkillHandler, mas mesmo assim consegue chamar o método UseSpell() indiretamente.

Ainda na classe SkillHandler, é importante adicionar o seguinte código:


Dessa maneira, quando a classe SkillHandler for desativada por qualquer motivo que seja, ela se desvinculará do evento do InputHandler, liberando uma referência na memória, e assim o método UseSpell não será mais chamado pelo InputHandler quando o evento for chamado.

Seguindo a mesma lógica da classe SkillHandler, poderíamos criar uma classe chamada... sei la, WeatherHandler, que muda o clima de acordo com as habilidades usadas. O código dessa classe inicialmente seria bem semelhante ao código da classe SkillHandler:


Dentro do método ChangeWeather(), seria necessário verificar como efetuar a mudança do clima, pra que clima deveríamos mudar (chuva, sol, vento, etc), enfim, todas as checagens referentes a mudar o clima seriam feitas aqui. E, assim como UseSpell na classe SkillHandler, esse método será automaticamente chamado assim que o evento onSpellUse() for chamado no InputHandler.

É possível criar inúmeros registros em apenas um evento, e dessa forma fazer com que várias classes respondam.

Um projeto básico de exemplo está disponível aqui, com as classes mencionadas neste post. Qualquer dúvida ou sugestão, deixa um comentário! Se quiser me esclarecer sobre outros usos de delegates e eventos, eu fico muito agradecido!

Grande abraço,
Allan

Nenhum comentário:

Postar um comentário