Tutorial: Jogos em HTML5 – Parte 2: Eventos


No último tutorial, mostrei a tag Canvas e alguns comandos do Javascript para você desenhar alguns tipos de primitivos.
A partir deste tutorial, vamos fazer algo mais prático: usarei, como um estudo de caso, o jogo Breakout, que é um jogo bem simples, porém contempla bastantes áreas que serão úteis para diversos jogos.
Então, vamos começar a construção do jogo falando de Eventos, que apesar de não ser uma novidade no HTML, é muito importante para o desenvolvimento de jogos.

O que são Eventos?

Eventos são funcionalidades que respondem quando o usuário ou outros programas interagem com o site. Por exemplo, quando o usuário clica em uma parte de um site, um evento onclick será disparado. Outro exemplos são os eventos onload, quando o site é totalmente carregado, entre outros. O programador, a partir do projeto, vê quais são as interações com o jogador e cria as rotinas que serão disparadas quando tais eventos ocorrerem. Por exemplo, quando um procedimento depender de algo que está fora do controle (como se um recurso já está carregado ou não), com certeza um evento será disparado para avisar que tais ações foram concluídas.

Listarei os principais eventos que acredito que sejam os mais importantes para o desenvolvimento de jogos:

  • onload: ocorre quando a página for completamente carregada. Para evitar problemas com recursos, recomendo que o jogo comece a ser executado somente após ocorrer esse evento;
  • onkeydown e onkeyup: estes dois eventos têm relação com os botões do teclado. Quando uma tecla é pressionada, um evento onkeydown acontece. Esse evento ocorre várias vezes enquanto o botão  estiver pressionado. Quando uma tecla é solta, ocorre o evento onkeyup.
  • onclick: quando o jogador clica em um elemento;
  • ondblclick: quando o jogador dá dois cliques em um elemento;
  • onmousedown e onmouseup: semelhante ao onkeydown e onkeyup, sendo chamado quando um botão do mouse é apertado (onmousedown) ou solto (onmouseup);
  • onmouseover e onmouseout: quando o ponteiro do mouse sobrepõe um elemento, ocorre o evento onmouseover. Quando o ponteiro deixa de sobrepor, ocorre um evento onmouseout.

Existem duas formas de atribuir uma função a um evento. O primeiro é no próprio arquivo HTML, como foi feito no primeiro tutorial, onde se coloca o nome do evento como um atributo e o nome da função, já devidamente declarada, como seu valor:

Exemplo 1: Uma função sendo atribuída a um evento pelo código HTML:

1  <body onLoad=”minhaFuncao()”>

A segunda  forma é atribuir pelo Javascript, em que muitos consideram mais seguro e mais organizado, pois estará separando os códigos HTML dos outros códigos. Um evento sempre estará atribuído a algum elemento HTML. No Exemplo 1, atribuímos o evento no elemento Body da página. Para fazer a mesma coisa no Javascript, fazemos o seguinte:

Exemplo 2: uma função sendo atribuída a um evento pelo Javascript:

1 window.onload = minhaFuncao;

No Exemplo 2, nós estamos atribuindo ao elemento window a função minhaFuncao. O elemento window representa a janela do navegador, que apesar de não ser o elemento Body propriamente dito, é a raíz de todos os elementos HTML e portanto, surtirá o mesmo efeito.

A função que servirá como resposta ao evento pode ser declarada de qualquer forma. Entretanto, o sistema sempre enviará um parâmetro, no qual se o programador quiser utilizar para saber algum detalhe de evento, deve prever esse parâmetro, como mostra o exemplo 3:

Exemplo 3: declaração de uma função com um parâmetro chamado “evt”

1 function minhaFuncao(evt){
2   //Aqui vem a função
3 }

Aqui, estou dizendo que quando essa função for invocada, espero receber um parâmetro e que referenciarei a esse parâmetro como “evt”. As informações de eventos que posso obter com esse parâmetro dependem do tipo de evento que foi executado.

 

O jogo Breakout

A jogabilidade do Breakout é muito simples: controlamos um retângulo que se move para os lados para rebater uma bolinha que percorrerá a tela. O objetivo do jogo é quebrar todos os tijolos que ficam no topo da tela usando a bolinha sem que a mesma toque a parte de baixo. No tutorial de hoje, nós vamos criar o retângulo que o jogador controla e, nos próximos tutoriais, vamos adicionando mais funcionalidades.

Primeiro, vamos criar um arquivo HTML, que chamarei neste exemplo de “tutorial.html”. Nele conterá uma canvas com bordas de tamanho 400 x 600. Vou colocar o código direto aqui, mas para explicações sobre ele, veja o nosso primeiro tutorial:

tutorial.html:

1 <!DOCTYPE HTML>
2 <html>
3  <head>
4    <script src=”tutorial.js” ></script>
5  <head>
6  <body>
7   <canvas id=”game” width=400 height=600 style=”border:solid”>
8    Seu navegador não suporta HTML 5
9   </canvas>
10  </body>
11 </html>

Se executarmos o HTML agora, o resultado deve ser semelhante a seguinte imagem:

Agora que a Canvas está pronta, vamos para o código do Javascript, que como vocês deve ter percebido no código HTML, vou chamá-lo de “tutorial.js”. Crie o arquivo JS e salve no mesmo local do arquivo HTML.

 

O Código

Falarei agora sobre o código e como vai funcionar. Eu classifiquei nesse tutorial 3 tipos de blocos de códigos: definições globais, declaração de funções e declaração de classes. A ordem que eles aparecem no código geralmente não importa. As raras exceções são quando envolvem alguma definição global, cujo valor que lhe é atribuído, (sem vírgula) depende de outra definição global. Neste tutorial não teremos nenhum caso que seja necessária esta dependência, e caso seja necessária futuramente, indicarei a ordem nas linhas de códigos.

Como foi visto no primeiro tutorial, a primeira coisa que devemos fazer quando vamos mexer com a Canvas, é adquirir seu contexto. Como o contexto é útil para todos, vamos criar uma variável global que será responsável por armazená-lo.

Declaração Globais:

1  var contexto;

Não inicializaremos agora, pois como foi dito no primeiro tutorial, o contexto é algo que pertence ao elemento Canvas, que só estará disponível quando a página estiver completamente carregada. Então vamos preparar uma função que será uma resposta ao evento onload. Quando a página for carregada, ela pegará as informações do contexto da canvas. O Código da função ficará assim:

Função iniciar:

1 function iniciar(){
2   var canvas = document.getElementById(“game”);
3   contexto = canvas.getContext(“2d”);
4 }

Para que a função seja executada no momento correto, adicionaremos nas declarações globais o comando para vincular a função ao evento onload:

Declaração Globais:

1 window.onload = iniciar;

Então, a partir de agora, a função iniciar() sempre será executada quando a página do jogo estiver totalmente carregada.

 

Jogador no Controle

Agora que já preparamos a Canvas, vamos preparar o objeto que o jogador controlará no jogo. Como foi dito na descrição, o objeto será um retângulo que poderá se mexer para os lados. Como perceberam pela frase anterior, nós criaremos uma classe para o retângulo. Como ele servirá para rebater a bolinha, chamaremos essa classe de Raquete.

Classe Raquete:

1 function Raquete(x, y, largura, altura){
2   this.x = x;
3   this.y = y;
4   this.largura = largura;
5   this.altura = altura;
6 }

Uma declaração de classe geralmente é extensa, porém é mais organizado do que se deixássemos tudo misturado. Ela parece que é uma declaração de função comum que, neste caso, possui quatro parâmetros:

  • x: a posição x do canto superior esquerdo;
  • y: a posição y do canto superior esquerdo;
  • largura: a largura da raquete;
  • altura: neste caso, seria a espessura da raquete.

Mas, a diferença começa pelos procedimentos. Podemos ver nas linhas 2 até 5 que ocorre a criação das propriedades dos objetos. A palavra chave this, está se referindo ao próprio objeto Raquete (Lembrando da diferença entre classe e objeto, classe é a declaração, objeto é a classe instanciada) e o que está ao lado direito do ponto, a propriedade em si. Note que as propriedades agem exatamente iguais as variáveis, isso porque elas são variáveis, com a diferença que se nós as alterarmos, a raquete será alterada também.

Mas, não somente de propriedades é formado uma classe. Ele também pode possuir comportamentos específicos, chamados de métodos. Vamos criar um método para a nossa raquete para que ela seja desenhada na tela:

Objeto Raquete:

1 function Raquete(x, y, largura, altura){
2   this.x = x;
3   this.y = y;
4   this.largura = largura;
5   this.altura = altura;
6  
7  this.pintar = function(){
8    contexto.fillRect(this.x, this.y, this.largura, this.altura);
9  }
10 }

Então, aqui nós adicionamos o método pintar. A principio, é a mesma coisa que fizemos com os atributos. Entretanto, estamos guardando no método pintar, uma função que utilizará o contexto para pintar um retângulo usando as propriedades do próprio objeto. Agora que temos um método para pintar, faltam só mais duas coisas para ser executado: a primeira é a instanciação do objeto, ou seja, a criação do objeto propriamente dito na memória do computador. A segunda ação é executar o método através do objeto instanciado. Vamos começar pelo primeiro passo, adicionando a seguinte linha na parte de declarações globais:

Declarações Globais:

1  var jogador = new Raquete(150,580,100,10);

Aqui, nós instanciamos a Raquete na variável global jogador. A novidade aqui é a palavra “new” na frente da “Raquete”. Esse “new” indica que a função seguinte é um objeto e não uma função qualquer. E neste caso, é um objeto da classe Raquete que está na posição (150, 580) e possui 100 pixels de largura e 10 de altura. Agora que temos a nossa raquete alocada na memória, vamos pedir para que desenhe a raquete quando o jogo for carregado, adicionando uma linha a mais na função iniciar():

Função iniciar:

1 function iniciar(){
2   var canvas = document.getElementById(“game”);
3   contexto = canvas.getContext(“2d”);
4   jogador.pintar();
5 }

Adicionamos na linha 4, um comando que utilizará o método pintar() do objeto da classe Raquete guardado pelo jogador. Aqui, se testarmos o jogo, já deve aparecer a seguinte tela:

Então, agora a única coisa que falta é fazer a raquete se mexer.

 

Eventos do Teclado

Para fazer a raquete se mover para os lados, precisamos primeiro detectar o que vai fazer movê-la. No nosso caso, vamos utilizar as teclas seta para esquerda e seta para direita e, consequentemente, precisamos do evento onkeydown. Mas, antes disso, precisamos definir como a raquete vai se mover. Para isso, criaremos mais um método na nossa classe Raquete, que chamaremos de mover():

Objeto Raquete:

1 function Raquete(x, y, largura, altura){
2   this.x = x;
3   this.y = y;
4   this.largura = largura;
5   this.altura = altura;
6  
7  this.pintar = function(){
8    contexto.fillRect(this.x, this.y, this.largura, this.altura);
9  }
10  
11  this.mover = function(dx){
12    this.x+=dx;
13  }
14 }

Nas linhas 11 até 13 é a definição do método mover. Fizemos com que ele receba um parâmetro chamado “dx” e o somaremos com a posição x, dando uma nova posição x. Em outras palavras, na linha 14 damos o comando de adicionar “dx” no próprio “x”.

Agora que temos o nosso método mover(), vamos criar a função “teclaPressionada” com um parâmetro para podermos ler o evento recebido. O código ficará assim:
Função teclaPressionada:

1 function teclaPressionada(evento){
2   switch(evento.keyCode){
3     case 37:
4       jogador.mover(-2);
5       break;
6     case 39:
7       jogador.mover(2);
8       break;
9    }
10 }

Aqui nós temos a nossa função. Na linha 2, nós iniciamos uma estrutura condicional switch que vai analisar o dado keyCode do evento. O keyCode mostra o código da tecla que foi pressionada. Obviamente, precisamos saber o código de cada tecla e isso pode variar de acordo com o tipo de teclado usado. No meu caso, uso o padrão ABNT2 que diz que a tecla da seta para esquerda tem valor 37 e a seta para direita tem o valor 39. Felizmente, esses códigos são comuns aos muitos tipos de teclado. As teclas que tem mais chances de possuir códigos diferentes são aquelas que são exclusivas de um tipo, por exemplo, a tecla “ç” e caracteres que possam ser deslocados do padrão, como “[“ ou “]”. Por isso, é bom utilizar as teclas padrões no seu jogo de HTML5 para os tipos mais comuns de teclados ou permitir que o jogador altere as configurações de teclas, que permite acessibilidade total.

Voltando para o código, nas linhas 3 e 4, dizemos que se o código do teclado for 37, o jogador deve mover-se dois pixels para esquerda (Lembre-se do plano cartesiano do tutorial passado). Caso o código do teclado for 39, o jogador deve mover a raquete dois pixels para a direita. O comando break no final de cada um desses comandos é obrigatório, pois caso seja omitido, se o código for 37, ele executará o caso 37 e 39. Para o segundo caso é indiferente, pois é o último caso a ser tratado. Entretanto, se nós colocarmos mais casos no futuro, por exemplo, para uma tecla que faz pausa do jogo, então precisaria do break para não executar o código para pausa.

Está quase pronto, agora precisamos vincular a função ao evento. Para isso, precisamos só fazer uma declaração global:

Declarações Globais:

1 window.onkeydown = teclaPressionada;

Podemos testar o jogo agora, entretanto, tem um detalhe: podemos pressionar as teclas o quanto queremos, mas não muda nada. Isso ocorre porque nós alteramos os dados do objeto, mas não pedimos para o navegador atualizar as imagens para a canvas. Então, vamos corrigir isso!

Para um iniciante, a coisa mais óbvia seria após a atualização dos dados, pintar novamente a raquete. Isso está parcialmente correto, entretanto, o resultado seria a raquete deixando um rastro por onde passou. Para evitar este rastro, primeiro devemos apagar o cenário e daí sim desenhar novamente. Vamos criar uma função “pintar” e então explico o código:
Função pintar:

1 function pintar(){
2   contexto.clearRect(0,0,400,600);
3   jogador.pintar();
4 }

Como podemos ver, primeiro nós usamos a função clearRect, que funciona que nem a função fillRect, só que ao invés de desenhar um retângulo, ele vai apagar a região do retângulo. Neste exemplo, dizemos que ele vai apagar a região que começa no ponto (0,0) com 400 de largura e 600 de altura, em outras palavras, toda a região da canvas. Com a canvas limpa, agora sim, mando pintar a raquete do jogador. Bem, para finalizar o código, agora invocaremos a função “pintar” no final da função “teclaPressionada” e, a partir daqui, a raquete estará se movendo perfeitamente.

Função teclaPressionada:

1 function teclaPressionada(evento){
2   switch(evento.keyCode){
3     case 37:
4       jogador.mover(-2);
5       break;
6     case 39:
7       jogador.mover(2);
8       break;
9    }
10    pintar();
11 }

 

Considerações Finais

No tutorial de hoje, aprendemos o que são eventos no Javascript e como utilizá-los para jogos. Existem diversos eventos, desde uma resposta aguardada pelo sistema (onload) como também ações do jogador (onkeydown, onkeyup, onmousedown etc). Iniciamos também o nosso jogo de Breakout, implementando a primeira funcionalidade que é mover a raquete do jogo. Com isso, aprofundamos no evento onkeydown, mostrando como verificar se uma tecla foi apertada com o atributo keyCode. Complemento que a mesma propriedade pode ser vista em um evento onkeyup, onde a diferença é que o atributo vai estar indicando qual foi a tecla que acabou de ser solta.

De bônus, neste tutorial aprendemos como criar uma classe no Javascript. O Javascript não é planejado para ser orientado a objeto e portanto, pode não ter recursos que uma linguagem orientada a objetos teria, mas é uma ótima forma de manter o código organizado e de fácil compreensão.

Então pessoal, até semana que vem, onde faremos mais uma parte do nosso jogo.

 

Thalisson Christiano de Almeida

Thalisson Christiano de Almeida

Formado em Ciência da Computação (UDESC). Foi Programador da Céu Games e professor do Técnico em Informática do SENAI-SC. Atualmente, trabalha na empresa By Seven. Já foi jogador de xadrez e praticou kung-fu, ambos por 4 anos. Hoje é praticante do Jiu-jitsu, esperando que não fique nos 4 anos. Não tem preferência de tipos de jogos em especifico, variando desde jogos casuais de Facebook até jogos mais hardcore.

Send this to a friend