Preview only show first 10 pages with watermark. For full document please download

Pong Em Javascript

Implementação do jogo Pong em JavaScript

   EMBED


Share

Transcript

Programando com PONG DHTML Jogador 1: Jogador 2: Diego A. Oliveira Sumário 1 O Que é Esse DHTML? 2 E Qual a Desvantagem do DHTML? 2.1 Por Que O PONG? . . . . . . . . . . . 2.2 O Que Vamos Precisar? . . . . . . . 2.3 O Canvas . . . . . . . . . . . . . . . . 2.4 Visualizando o Canvas . . . . . . . . 2.5 Desenho no Canvas . . . . . . . . . 2.6 Final de Seção . . . . . . . . . . . . . 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 . 5 . 5 . 5 . 8 . 9 . 13 3 OBJETOS NO JAVASCRIPT 14 3.1 O Objeto Bola . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.2 Placar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 3.3 Final de Seção . . . . . . . . . . . . . . . . . . . . . . . . . . 22 4 O Loop de Animação 24 4.1 Desenhando Linhas . . . . . . . . . . . . . . . . . . . . . . 26 4.2 Final de Seção . . . . . . . . . . . . . . . . . . . . . . . . . . 28 5 DETECÇÃO DE TECLA 30 5.1 Final de Seção . . . . . . . . . . . . . . . . . . . . . . . . . . 34 6 MOVIMENTO DAS BARRAS 35 6.1 Explicando o Que Aconteceu . . . . . . . . . . . . . . . . . 36 6.2 Final de Seção . . . . . . . . . . . . . . . . . . . . . . . . . . 39 7 MOVIMENTO DA BOLA E COLISÃO 42 7.1 Velocidade da Bola . . . . . . . . . . . . . . . . . . . . . . . 46 7.2 Fim de Seção . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 8 Placar 51 8.1 Final de Seção . . . . . . . . . . . . . . . . . . . . . . . . . . 53 9 MELHORANDO A PERFORMASSE [Opcional] 56 10 LIVROS INDICADOS 58 Introdução e Avisos Se você já fez disciplinas como algoritmo, lógica de programação ou mesmo cálculo numérico então você já deu o primeiro passo para o mundo da programação, isso mesmo o primeiro passo. Isso porque estas disciplinas trabalham apenas o básico e estão bem longe do que se deseja para formar um programador de nível mediano. Se no entanto, você nunca teve nenhum contato com nenhuma linguagem pare a leitura desta apostila e procure em uma biblioteca, ou mesmo na internet aulas sobre lógica de programação. Todos os livros ou tutoriais sobre jogos, por mais básico que sejam e venha com frases como: "aprenda passo a passo", "a partir do básico", "for dummies" e etc., requerem um conhecimento razoável pelo menos em lógica de programação. E se você não o têm, não conseguirá entender completamente o seu conteúdo. E esta apostila não é diferente. 3 1 O Que é Esse DHTML? O DHTML (Dinamic Hiper Text Markup Language) é a junção de três outras linguagens o JavaScript, o Css e o HTML. Mas, jogos em DHTML na verdade levam muito pouco de HTML e Css a maior parte do trabalho fica mesmo com o JavaScript. A vantagem do JavaScript em relação as demais linguagens é que ela é interpretada pelo browser. O que significa que desde que você tenha um OS com um browser moderno instalado será capaz de testar seu trabalho. Outra vantagem é que você não precisa instalar nada em sua máquina, pois você necessitará apenas de um editor simples de texto. Por fim o JavaScript tem uma excelente curva de aprendizado. São tantos recursos que em pouco tempo você já será capaz de fazer coisas impressionantes. 2 E Qual a Desvantagem do DHTML? Existem várias linguagens de programação e apesar do que muita gente acredita, eu já vi até professores universitários afirmarem isso, elas não são todas iguais. Cada linguagem possui uma característica própria que facilita uma ou outra tarefa. Temos que nos lembrar que o JavaScript foi criado para dar interação a páginas Web e não para criação de jogos. Assim, temos de lidar com algumas limitações. Por exemplo: ˆ O JavaScript não trabalha com ambientação 3D; ˆ Como é interpretada pelo browser possui as limitações gráficas do browser; ˆ O debug é difícil; ˆ É necessário testar seus códigos em vários navegadores. A linguagem mais indicada para criação de jogos, segundo alguns profissionais, é a linguagem C. Esta linguagem é normalmente usada para programação de sistemas que envolvam processamento pesado. Mas, se você pretende desenvolver jogos simples que não exijam muito do hardware pode perfeitamente utilizar-se do JavaScript. 4 2.1 Por Que O PONG? O PONG é um jogo relativamente simples de programar, por isso muitos livros de programação o apresentam em suas páginas finais. Sendo simples é um excelente ponto de partida para quem está começando. 2.2 O Que Vamos Precisar? Para desenvolver com o DHTML você precisa apenas de um navegador atualizado e um editor de texto simples. Eu recomendo o Sublime Text como editor de texto e o Google Chrome como navegador. No entanto, você pode usar o bloco de notas e o IE. O problema é que o IE só conseguem interpretar bem o DHTML a partir da sua 9 versão (IE9). O que você não deve fazer é usar o Word ou WordPad como editores. Isso porque esses programas têm o habito de "mudar" o texto que escrevemos. Como já disse, o HTML5 é uma tecnologia que ainda está sendo implementada nos navegadores de modo que é bom ter um segundo navegador para testar. Como segundo navegador eu recomendo o Firefox. Um programa que funciona bem no Chrome e no Firefox normalmente bem funciona nos demais. 2.3 O Canvas O Canvas é uma tecnologia presente nas especificações do HTML5 e já está implementada em todos os navegadores inclusive no IE9. Essa tecnologia surgiu com a proposta de adicionar mais interatividade a páginas web, querendo inclusive substituir o Flash como recurso multimídia. A forma mais simples de se pensar no Canvas é como uma folha de papel em branco. Nesta folha é que "desenhamos" ou colocamos os elementos do jogo. Existe mais de uma forma de criar o elemento Canvas em uma página HTML, veja: 5 USANDO A TAG CANVAS: 1 2 3 USANDO JAVASCRIPT: 1 2 3 4 5 6 7 8 No primeiro exemplo criamos o elemento Canvas usando a tag , assim como faríamos como um elemento qualquer do HTML. Note que foi dado ao canvas um id (identificador) e os atributos width e height (largura e altura). Esses três atributos (id, width e height) são indispensáveis para se trabalhar com o Canvas. A vantagem dessa abordagem é que podemos colocar alguma mensagem indicando que o browser não suporta essa tecnologia entre as tags. Caso o browser a suporte, esse conteúdo é ignorado: 1 2 3 4 5 O navegador nao suporta o Canvas. O segundo exemplo apresenta o mesmo resultado que o primeiro. Note que também precisamos informar uma id e valores para os atributos width e height, assim como no exemplo anterior. Embora essa ultima abordagem seja um pouco mais complicada ela possui uma vantagem. Podemos, por exemplo, criar o elemento Canvas de acordo com o tamanho da tela do browser do seu PC, celular ou tablet. 6 1 2 3 4 5 6 7 8 9 10 11 12 O innerHeight retorna a altura da tela do browser em seu dispositivo enquanto o innerWidth a largura. Se (if) a largura for maior ou igual a 700 pixel então as dimensões do Canvas será de 600x480 pixeis. Caso contrário (else), será de 400x320 pixeis. Como esse tutorial não é voltado para tablets ou celulares vamos usar o Canvas por meio das tags. Mas, o leitor deve ficar a vontade em escolher uma outra forma. Se o Canvas é um elemento do HTML posso passar os valores de width e height usando css? Se esta pergunta passou pela sua cabeça então é a resposta para ela é SIM e NÃO. O Canvas pode receber dimensões também via Css, no entanto, evite ao máximo fazer isso. Por motivos que não será discutido aqui, o resultado disso quase sempre são imagens desfocadas e feias na sua tela. E não queremos um jogo com imagens assim. 7 2.4 Visualizando o Canvas O código a seguir cria um Canvas com dimensões iguais a 600x480 pixels. 1 O navegador nao suporta o Canvas. Contudo, se você abrir o seu navegador não verá absolutamente nada. Na verdade, o Canvas já está presente você apenas não consegue vê-lo. Para conseguirmos ver o Canvas podemos recorrer ao Css. 1 2 3 4 5 6 7 8 9 canvas{ position: absolute; top: 0px; bottom: 0px; left: 0px; right: 0px; margin: auto; background-color: black; } O css acima não só centraliza o Canvas como também colocalhe um fundo negro. Se você abrir agora seu navegador verá um retângulo negro. Esse retângulo é o Canvas e consequentemente a tela do jogo. 8 2.5 Desenho no Canvas Para desenhar no Canvas usamos o método getContext. Esse método possui vários outros métodos que nos permitem desenhar várias formas como quadrados, círculos e etc.. Entretanto, esse método é um pouco complicado de se usar, assim, para facilitar o seu uso, é muito comum guardar suas configurações em uma variável veja como: 1 2 3 4 Primeiro criamos a variável global canvas ("c" minusculo) e nela guardamos as configurações do elemento cuja id foi passada. No caso o Canvas. Em seguida criamos outra variável global chamada ctx (context) que recebe o método getContext do canvas. Note que passamos como parâmetro ao método getContext, uma string identificando o contexto desejado. No caso o contexto 2d ("d" em minusculo). Agora que temos as configurações do getContext na variável ctx podemos acessar os métodos do getContext como muita facilidade. Veja por exemplo como podemos usar os métodos fillStyle e fillRect para desenhar um quadrado vermelho. 1 2 3 O resultado será o seguinte: 9 O primeiro método indica a cor que será usada para desenhar no Canvas. //Síntese do método ctx.fillStyle = ’cor’; O segundo método (fillRect), desenha retângulos. //Síntese do método ctx.fillRect(x, y, w, h); Os valores x e y correspondem a posição do canto superior esquerdo do retângulo. O w a largura e h a altura. 10 As coordenadas para a tela do navegador funcionam como na figura acima. Para desenhos um pouco mais complexos, como um circulo, necessitamos criar um patch (caminho). Veja a seguir como desenhar um circulo de diâmetro igual a 14. 1 2 3 4 5 ctx.beginPath(); /* Inicio do patch */ ctx.arc(50, 50, 14, 0, Math.PI*2, true); ctx.closePath(); /* Fim do Patch */ ctx.fillStyle = ’red’; ctx.fill(); Primeiro iniciamos o Patch e dentro dele colocamos o script necessário para desenhar a bola. Em seguida fechamos o pacth. Contudo, a criação do pacth não implica no desenho de nada no seu Canvas. Por isso usamos o fill logo abaixo. Ele é que responsável por desenhar o conteúdo do patch dentro do Canvas. 11 Como o Pong usa apenas barras e uma bola não irei explorar essa parte de desenho no Canvas mas, muita coisa legal pode ser desenhada usando patch. Basta procurar no Google para ver. 12 2.6 Final de Seção No final de cada seção será dado o código completo desenvolvido até o momento. Isso servirá tanto para que você possa acompanhar o próximo capitulo, caso você venha a se perder em alguma passagem, quanto para que você consiga se organizar melhor. Até o momento o que temos do nosso jogo é o seguinte: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Pong com HTML5 e JavaScript O navegador nao suporta o Canvas do HTML5. 13 3 OBJETOS NO JAVASCRIPT Não é meu objetivo explicar detalhadamente o que são objetos no JavaScript, assim ensinarei apenas o necessário para que prossigamos. Mas, antes veja a cara final do jogo que vamos fazer. Para criar a raquete esquerda poderíamos simplesmente escrever. 1 ctx.fillRect(560,190,25,100); Onde 560 seria a coordenada x, 190 a coordenada y em que a raquete começaria a ser desenhada. O valor 25 é a largura da barra e 100 a altura que ela terá. Contudo, existe uma forma mais eficiente. Primeiro criamos uma função que deverá receber por valor todos os valores necessários para desenhar a raquete. 14 1 2 3 4 5 6 function Raquete(x,y,larg,alt){ this.x = x; this.y = y; this.largura = larg; this.altura = alt; }; A esse tipo de função damos o nome de classe. Em seguida guardamos essa classe dentro de uma variável, chamada playerR (R de right) e passamos a função Raquete os parâmetros solicitados. A esse processo damos o nome de instância. 1 2 3 4 5 6 function Raquete(x,y,larg,alt){ this.x = x; this.y = y; this.largura = larg; this.altura = alt; }; 7 8 var playerR = new Raquete(560,190,25,100); Um objeto nada mais é do que uma classe instanciada. Assim o que acabamos de criar foi um objeto chamado playerR. Agora ao invés de usar 1 ctx.fillRect(560,190,25,100); Podemos usar 1 ctx.fillRect(playerR.x,playerR.y,playerR.largura,playerR. altura); Ambos terão o mesmo efeito. Usar objetos deixa o código bem mais organizado e agiliza em certas ocasiões a escrita de seus scripts. Mesmo que você tenha que escrever um pouco mais vale a pena. Outra coisa interessante sobre objetos é que podemos passar funções como atributos utilizando os chamados protótipos. Por exemplo, abaixo da função Raquete escreva o seguinte: 15 1 2 3 4 5 6 7 function Raquete(x,y,larg,alt){ this.x = x; this.y = y; this.largura = larg; this.altura = alt; }; Raquete.prototype.draw = function(){ 8 9 }; O que acabamos de fazer foi criar um método para a classe Raquete. No momento ele está vazio mas, faremos com que esse método desenhe a raquete sempre que requisitado. 1 2 3 4 5 6 7 8 9 10 function Raquete(x,y,larg,alt){ this.x = x; this.y = y; this.largura = larg; this.altura = alt; }; Raquete.prototype.draw = function(){ ctx.fillStyle = ’white’; ctx.fillRect(this.x,this.y,this.largura,this.altura); }; O this indica que o fillRect está recebendo o valor da classe ao qual o método pertence. Agora quando quisermos desenhar a raquete podemos escrever somente: playerR.draw(); O trecho de código a seguir mostra a raquete da direita desenhada no Canvas. 1 2 3 4 5 6 7 8 9 10 function Raquete(x,y,larg,alt){ this.x = x; this.y = y; this.largura = larg; this.altura = alt; }; Raquete.prototype.draw = function(){ ctx.fillStyle = ’white’; ctx.fillRect(this.x,this.y,this.largura,this.altura); }; 11 16 12 var playerR = new Raquete(560,190,25,100); 13 14 15 /* Chamando o metodo draw */ playerR.draw(); Raquete mais a direita. Note que usamos dentro do método draw a variável ctx. Como estamos utilizando todo o javascript num único arquivo e ctx é global não teremos nenhum problema. Mas, se estivéssemos escrevendo o javascript em um arquivo externo o ideal seria passar a classe também á variável. Para que você se acostume a fazer atualize sua classe Raquete para receber a variável ctx. 1 2 3 4 5 6 7 8 9 10 function Raquete(x,y,larg,alt,ctx){ this.x = x; this.y = y; this.largura = larg; this.altura = alt; /*Recebendo ctx*/ this.ctx = ctx; }; Raquete.prototype.draw = function(){ /* o metodo draw tambem se altera */ 17 11 12 13 this.ctx.fillStyle = ’white’; this.ctx.fillRect(this.x,this.y,this.largura,this.altura) ; }; 14 15 16 /* Passando ctx */ var playerR = new Raquete(560,190,25,100,ctx); Uma vez que criamos a raquete da direita criar a raquete da esquerda é fácil. Isso, porque podemos aproveitar a classe Raquete que criamos. Para desenhar a raquete esquerda escrevemos apena o seguinte. 1 2 3 4 /* Cria o objeto playerL (L de left) */ var playerL = new Raquete(10, 190, 25, 100,ctx); /* Desenha o objeto no Canvas */ playerL.draw(); Raquetes do jogo. Viu como foi simples. A maior vantagem de se trabalhar com objetos, além da organização, é a possibilidade de aproveitar ao máximo o que já foi escrito. 18 3.1 O Objeto Bola Vamos dar uma olhada no nosso script até agora. 1 2 3 4 5 6 7 8 9 10 11 12 13 function Raquete(x,y,larg,alt,ctx){ this.x = x; this.y = y; this.largura = larg; this.altura = alt; this.ctx = ctx; }; Raquete.prototype.draw = function(){ this.ctx.fillStyle = ’white’; this.ctx.fillRect(this.x,this.y,this.largura,this.altura) ; }; var playerR = new Raquete(560,190,25,100,ctx); var playerL = new Raquete(10, 190, 25, 100,ctx); 14 15 16 playerR.draw(); playerL.draw(); Antes de continuar certifique-se que você é capaz de entender cada linha. Se você compreendeu bem essa parte de objetos você não terá dificuldade em entender o código a seguir que implementa o objeto bola e o desenha no canvas quando o método draw é solicitado. 1 2 3 4 5 6 7 8 9 10 11 12 13 function Boll(x,y,diametro,ctx){ this.x = x; this.y = y; this.diametro = diametro; this.ctx = ctx; }; Boll.prototype.draw = function(){ this.ctx.beginPath(); this.ctx.arc(this.x, this.y, this.diametro, 0, Math.PI*2, true); this.ctx.closePath(); this.ctx.fillStyle = ’white’; this.ctx.fill(); }; 14 15 var bola = new Boll(50, 190, 14, ctx); 19 16 17 bola.draw(); Bola decentralizada. O script acima gera uma bola decentralizada. Para resolver isso basta alterar os valores do objeto bola para isso: 1 var bola = new Boll(canvas.width/2, canvas.height/2, 14, ctx); Agora a bola ficara no meio do Canvas. Lembre-se, a variável canvas guarda as configurações do canvas que criamos assim canvas.width retorna o valor do width que fornecemos para o Canvas e a barra (/) seguida pelo dois divide esse valor pela metade. 20 3.2 Placar Cada raquete possui um placar a ele associado. Vamos criar mais um método para desenhar esses placares. 1 2 3 4 5 6 7 8 9 10 11 Raquete.prototype.score = function(x,y,player){ this.placar = 0; this.xPlacar = x; this.yPlacar = y; this.player = player; /* Desenhando o placar */ this.ctx.font = "20px Arial"; this.ctx.fillStyle = ’white’; this.ctx.fillText("Jogador " + this.player, this.xPlacar, this.yPlacar); this.ctx.fillText(": "+ this.placar, this.xPlacar+90, this.yPlacar); }; Agora, se quisermos escrever na tela o placar da raquete direita escrevemos playerR.score(x,y,player); onde o x e y são as coordenadas que o placar terá no Canvas e player o numero do jogador (1 ou 2). 21 3.3 Final de Seção Chegamos ao final do capítulo, e mais uma vez vamos parar e olhar tudo o que foi feito até agora. Uma dica que dou é: imprima a folha de códigos no final de cada capítulo e a tenha sempre em mãos. 1 2 3 23 4 O Loop de Animação Assim como um filme um jogo não é nada mais que uma sequencia de slide passando numa velocidade altíssima. Ainda não temos nenhuma estrutura desse tipo mas, implementa-la é algo simples. Na verdade, tudo que precisamos é de uma função que seja executada infinitamente de modo que a cada execução imprima a tela do nosso jogo. Até o momento a tela do nosso jogo existe apenas por meio dos métodos que criamos. São esses métodos que ficarão dentro da função em loop. 1 2 3 4 5 6 7 8 function Game(){ playerL.draw(); playerR.draw(); bola.draw(); playerL.score(50,20,1); playerR.score(440,20,2); setTimeout(Game, 24); }; 9 10 Game(); No código acima a função Game é chamada em loop e a cada vez que é executada chama os métodos que desenham a tela do jogo. Toda essa estrutura compõe o nosso loop de animação. E o responsável por isso é o comando setTimeout. O setTimeout recebe a função que entrará em loop e o tempo em milissegundos que usará para isso. Acima a função Game é chamada a cada 24 milissegundos. Agora vamos fazer uma experiência. Modifique seu script de modo a deixa-lo como no exemplo acima. Em seguida abra-o no seu navegador e durante alguns segundos observe o placar. Você vai perceber que as letras estão meio deformadas. Texto deformado. 24 Isso ocorre porque a função Game está desenhando cenário sobre cenário. A cada vez que ela é executada ela chama os métodos que redesenham a tela do jogo uma por sima da outra. Isso gerá rastros nos objetos que se movimentam, textos feios, e alto consumo de memória da sua máquina. Para evitar que isso ocorra devemos limpar o Canvas toda vez que a função Game for chamada. 1 2 3 4 5 6 7 8 9 function Game(){ ctx.clearRect(0,0,canvas.width, canvas.height); playerL.draw(); playerR.draw(); bola.draw(); playerL.score(50,20,1); playerR.score(440,20,2); setTimeout(Game, 24); }; 10 11 Game(); O clearRect limpa todo o Canvas formando um retângulo com coordenada superior esquerda em (0,0) e coordenada interior direita em (canvas.width, canvas.height). Sempre que uma função entrar em loop realizando o ciclo de animações no Canvas não se esqueça que você deve limpar o Canvas a cada ciclo. Abra agora o script em seu browser e verifique que o placar está bem mais elegante. 25 4.1 Desenhando Linhas Desenhamos uma linha dentro do Canvas usando Paths (caminhos). Já comentei um pouco sobre o pacth mais aqui vai uma explicação mais detalhada. Um path e um conjunto de comandos de desenho que ficam registrados na memória, aguardando os métodos fill (preencher) ou stroke (contornar) serem chamados. Veja na prática como uma linha é desenhada. 1 2 3 4 5 6 7 8 9 10 11 12 /* Inicia o modo de desenho */ ctx.beginPath(); /* Posiciona a caneta virtual no ponto (x,y) */ ctx.moveTo(canvas.width/2, 0); /* traca uma linha virtual do ponto atual ate o ponto indicado */ ctx.lineTo(canvas.width/2, canvas.height); /* Configurar a espessura da linha */ ctx.lineWidth = 2; /* Configura a cor da linha */ ctx.strokeStyle = ’white’; /* Traca fisicamente a linha virtual */ ctx.stroke(); Lembra-se que no jogo de Pong original existe uma linha vertical que divide o campo?! Então o nosso jogo também terá. Entretanto, como são muitas linhas para colocar dentro da função Game (e não é muito bom encher sua função principal com muito código), vamos criar uma segunda função chamada linha. 1 2 functio Linha(){ ctx.clearRect(0,0,canvas.width,canvas.height); 3 4 }; E dentro dela colocar o path para desenhar as linhas 1 2 3 4 5 6 function Linha(){ ctx.clearRect(0,0,canvas.width,canvas.height); ctx.beginPath(); ctx.moveTo(canvas.width/2, 0); ctx.lineTo(canvas.width/2, canvas.height); ctx.lineWidth = 2; 26 7 8 9 ctx.strokeStyle = ’white’; ctx.stroke(); }; Como a função Linha já possui um clearRect substituímos o clearRect da função Game pela chamada da função Linha. 1 2 3 4 5 6 7 8 9 function Game(){ Linha(); playerL.draw(); playerR.draw(); bola.draw(); playerL.score(50,20,1); playerR.score(440,20,2); setTimeout(Game, 24); }; O resultado é a tela oficial do nosso jogo. 27 4.2 Final de Seção Finalizada mais uma seção vamos rever todo o código escrito até agora. 1 2 3 29 5 DETECÇÃO DE TECLA Todo as ações do nosso PONG serão determinadas pelo teclado. Assim, devemos fazer com que o Canvas seja capaz de detectar quando uma tecla é pressionada e quando uma tecla que foi pressionada é solta. É aí que entra os ouvintes (Listener) do JavaScript. Um listener é uma função JavaScript que executa uma ação em resposta a um evento. Essa função pode receber um parâmetro que é preenchido automaticamente com dados sobre o evento ocorrido. Para associar um listener a um elemento da página, usamos o método addEventListener: Sua síntese é a seguinte: // Exemplo Teórico elemento.addEventListener(’evento’, função, false); Primeiro indicamos o elemento o qual o Listener será associado. Em seguida damos o nome do evento. Sempre que o evento indicado ocorrer o método irá chamar uma função (function callback). Essa função tem seu nome escrito após o nome do evento. Na pratica o código para a detecção de teclas fica assim: 1 2 3 4 5 6 7 /* Exemplo Pratico */ var teclas = {}; document.addEventListener(’keydown’, Keydown, false); function Keydown(evento){ teclas[evento.keyCode] = true; alert(evento.keyCode); }; No exemplo acima primeiro criamos um array vazio chamado teclas. A função dele será guardar um registro das teclas que forem pressionadas. Uma observação importante é que o addEventListener foi 30 aplicado ao document. Poderíamos aplica-lo diretamente ao elemento Canvas, mas caso o usuário por acaso clicasse fora do Canvas o método passaria a não funcionar. Aplicando-o a todo o documento garantimos que o método sempre estará disponível. Em JavaScript, são três os eventos associados ao teclado: keydown, keypress e keyup. Mas para jogos somente dois são importantes. ˆ keydown: ocorre quando uma tecla e pressionada (abaixada). Se o usuário a mantiver segurando, o evento e disparado repetidamente; ˆ keyup: ocorre uma única vez, quando uma tecla e solta. Veja mais uma vez o script para detecção de teclas que escrevemos. 1 2 3 4 5 6 var teclas = {}; document.addEventListener(’keydown’, Keydown, false); function Keydown(evento){ teclas[evento.keyCode] = true; alert(evento.keyCode); }; Observe que estamos usando o evento keydown assim, sempre que uma tecla do teclado é pressionada a função Keydown é chamada. No exemplo, nomeamos a função com o mesmo nome do evento (exceto pelo "K" maiúsculo), mas na prática essa função pode ter qualquer nome. E o que a função Keydown está fazendo? Vamos olha-la novamente. 1 2 3 4 function Keydown(evento){ teclas[evento.keyCode] = true; alert(evento.keyCode); }; A função Keydown seta para true os elementos do objeto teclas. Quando a função Keydown é chamada o AddEventListener automaticamente passa para ela o parâmetro evento. Você pode dar qualquer nome a esse parâmetro como, por exemplo, evt. 31 Esse parâmetro é o caractere digitado. O keycode fornece o código ASCII desse caractere. O código ASCII da tecla "W" por exemplo, é 83. Supondo que o usuário pressione a tecla "W" a função Keydown cria e seta para true o array teclas[83]. O valor lógico deste array é o que indicará que a tecla "W" foi pressionada. teclas[83] = true; Agora que você deve ter entendido como funciona a função Keycode atualize o seu código e teste no seu navegador pressionando, por exemplo, a tecla up. Como resultado deve ocorrer uma janela de alerta como a seguir. O alerta indica que a função Keydown está realmente recebendo o parâmetro evento e que seu valor é 38. Esse número é o código que identifica a tecla up na tabela ASCII. Com isso também criamos o array teclas[38] com valor igual a true. Que indica que a tecla foi pressionada. 32 Para fazer com que a tecla volte ao seu estado inicial (sem nenhum valor lógico) quando ela for solta, usamos mais um EventListener. Que pode ser declarado logo abaixo do primeiro. 1 2 3 4 5 /* Exemplo Pratico */ document.addEventListener(’keyup’, Keyup, false); function Keyup(evento){ delete teclas[evento.keyCode]; }; Agora usamos o evento keyUp. Pois a tecla só deve voltar ao seu estado inicial depois que ela for liberada. O delete esvazia o objeto teclas. Assim, se antes a variável teclas[38] tinha o valor true e o JavaScript reconhecia essa tecla como pressionada, quando ela é solta o evento keyUp dispara a função que deleta esse array do objeto, fazendo com que a tecla que foi pressionada volte para seu estado inicial. 33 5.1 Final de Seção Como nesse capítulo foram poucas as alterações feitas no nosso script será colocado apenas o trecho específico em que elas ocorreram. Isso será o suficiente para que você possa se localizar. 1 2 3 4 5 /* ...Codigos */ var playerR = new Raquete(560,190,25,100,ctx); var playerL = new Raquete(10, 190, 25, 100,ctx); var bola = new Boll(canvas.width/2, canvas.height/2, 14, ctx); var teclas = {}; 6 7 8 9 10 11 document.addEventListener(’keydown’, Keydown, false); function Keydown(evento){ teclas[evento.keyCode] = true; alert(evento.keyCode); }; 12 13 14 15 16 document.addEventListener(’keyup’, Keyup, false); function Keyup(evento){ delete teclas[evento.keyCode]; }; 17 18 19 20 21 22 23 24 25 26 function Linha(){ ctx.clearRect(0,0,canvas.width,canvas.height); ctx.beginPath(); ctx.moveTo(canvas.width/2, 0); ctx.lineTo(canvas.width/2, canvas.height); ctx.lineWidth = 2; ctx.strokeStyle = ’white’; ctx.stroke(); }; 27 28 /* Codigos... */ 34 6 MOVIMENTO DAS BARRAS No capítulo anterior fizemos com que o Canvas fosse capaz de reconhecer quando uma tecla é pressionada ou liberada. Agora faremos com que nossas raquetes se movam. Primeiro vamos dar movimento a raquete mais a direita. Faremos com que ela se desloque para cima quando pressionada a tecla Up e pare quando liberada a tecla. E faremos com que ela se mova para baixo quando pressionada a tecla Down e pare quando a mesma for liberada. A primeira coisa que faremos é retirar a função de alerta da função keydown 1 2 3 4 document.addEventListener(’keydown’, keydown, false); function keydown(evento){ teclas[evento.keyCode] = true; }; Agora vamos criar um novo método para a classe Raquete. Esse método irá ser responsável pelo movimento da raquete. 1 2 3 4 5 6 7 Raquete.prototype.move = function(speed,up,down){ this.speed = speed; this.up = up; this.down = down; if (this.up in teclas) this.y -= this.speed; }; O protótipo acima faz referência a uma variável chamada speed, essa variável será a velocidade com que a barra do jogo irá se deslocar. Agora dentro da função Game chame o método playerR.move(), passando o valor de speed, o código da tecla UP e o código da tecla Down. 35 1 2 3 4 5 6 7 8 9 10 11 function Game(){ Linha(); playerL.draw(); playerR.draw(); bola.draw(); playerL.score(50,20,1); playerR.score(440,20,2); /* Metodos de movimento */ playerR.move(5,38,40); setTimeout(Game, 24); }; E experimente executar seu script no navegador pressionando aos poucos a tecla up. Se você fez tudo certo verá que a barra da direita agora é capaz de deslizar para cima. 6.1 Explicando o Que Aconteceu No método move escrevemo o seguinte: 1 2 3 4 5 6 7 Raquete.prototype.move = function(speed){ this.speed = speed; this.up = up; this.down = down; if (this.up in teclas) this.y -= this.speed; }; 8 9 playerR.move(5,38,40); Se a tecla de código 38 estiver dentro do objeto teclas então a variável y do objeto playerR sofre um decremento igual ao valor da variável speed. O problema da implementação feita até agora é que a barra acaba passando da área do Canvas e não é isso que queremos que aconteça. Para resolver este problema atualizamos o método move para o seguinte. 36 1 2 3 4 5 6 7 Raquete.prototype.move(speed){ this.speed = speed; this.up = up; this.down = down; if(this.up in teclas && this.y > 0) this.y -= this.speed; }; Repare que agora a barra sobe até o limite do Canvas e em seguida para. Para descer a barra acrescentamos mais uma condição ao método. Como foi feito a seguir. 1 2 3 4 5 6 7 8 9 Raquete.prototype.move = function(speed){ this.speed = speed; this.up = up; this.down = down; if(this.up in teclas && this.y > 0) this.y -= this.speed; if(this.down in teclas && this.y + this.altura < canvas. height) this.y += this.speed; }; Para movimentar a barra esquerda basta chamar o método playerL.move() passando o valor de speed e das teclas W (87) e S(83). 1 2 3 4 5 6 7 8 9 10 11 12 function Game(){ Linha(); playerL.draw(); playerR.draw(); bola.draw(); playerL.score(50,20,1); playerR.score(440,20,2); /* Metodos de movimento */ playerR.move(5,38,40); playerL.move(5,87,83); setTimeout(Game, 24); }; 37 Se você testar o script acima agora as duas barras estarão se deslocando para cima e para baixo quando apertamos Up, Down, W ou S. 38 6.2 1 2 3 Final de Seção 41 7 MOVIMENTO DA BOLA E COLISÃO Agora vamos implementar um método para o movimento a bola. 1 2 3 4 Boll.prototype.move = function(){ this.x += this.speed * this.dirx; this.y += this.speed * this.diry; }; Ao contrário das barras a bola têm sua posição atualizada a todo momento e por isso o código acima aparece sem nenhuma estrutura condicional como o if. O método faz referência a três variáveis: dirx (muda a direção da bola quando a mesma se choca horizontalmente), diry (muda a direção da bola quando ela colide verticalmente) e speed (que controla a velocidade da bola. Como esses valores serão atualizados constantemente não podemos declara-los dentro do método. Temos que fazer isso dentro da classe. 1 2 3 4 5 6 7 8 9 function Boll(x,y,diametro,ctx){ this.x = x; this.y = y; this.diametro = diametro; this.ctx = ctx; this.dirx = -1; this.diry = 1; this.speed = 2; }; Agora podemos chamar o método criado dentro da função Game. Se você fizer isso já vai poder ver a bola se deslocando. No entanto, quando ela bate no piso ela desaparece ao invés de ser repelida, como deveria ser. 42 Para corrigir essa falha na colisão inserimos mais um if ao método move. 1 2 3 4 5 Boll.prototype.move = function(){ /*... codigos ...*/ if(this.y >= canvas.height) this.diry = -1; }; Observe que agora a bola é repelida quando toca o piso do Canvas. No entanto duas melhorias podem ser feitas aqui. Primeiro perceba que a bola entra um pouco antes de ser rebatida pelo piso. Isso porque this.y dado é referente ao centro da bola. Assim, podemos corrigir essa falha fazendo o seguinte. 43 1 2 3 4 5 Boll.prototype.move = function(){ /*... codigos ...*/ if(this.y >= canvas.height - this.diametro/2) this.diry = -1; }; Outra coisa que podemos fazer é tirar a dependência da classe Boll da variável canvas, assim como fizemos com a variável ctx na classe Raquete. 1 2 3 4 5 6 7 8 9 10 11 function Boll(x,y,diametro,ctx,canvas){ this.cnv = canvas; /* Para facilitar a escrita */ this.x = x; this.y = y; this.diametro = diametro; this.ctx = ctx; this.canvas = canvas; this.dirx = -1; this.diry = 1; this.speed = 2; }; E não esqueça de alterar o método para que receba a variável canvas de dentro da classe. 1 2 3 4 5 Boll.prototype.move = function(){ /*... codigos ...*/ if(this.y >= this.cnv.height - this.diametro/2) this.diry = -1; }; E de passar o canvas no método move. 1 bola.move(canvas); A direção da bola é controlada pelas variáveis diry e dirx. 44 Como a bola está descendo e queremos que ao tocar na borda inferior ela suba mudamos o valor da variável diry para seu oposto, isto é −1. Até agora implementamos apenas a colisão da bola com o piso inferior do Canvas. Contudo, a colisão com o piso superior e as paredes a direita e esquerda segue uma lógica bem similar. Sugiro que você tente implementa-las sozinho, mas caso não tenha sucesso a implementação completa de todas as colisões se encontra a seguir. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Boll.prototype.move = function(canvas){ this.canvas = canvas; this.x += this.speed*this.dirx; this.y += this.speed*this.diry; /* colisao com o piso */ if(this.y >= this.canvas.height - this.diametro/2) this.diry = -1; /* colisao com a barra esquerda */ if((this.x - this.diametro) <= (playerL.x + playerL. largura) && this.y >= playerL.y && this.y <= playerL.y + playerR.altura) this.dirx = 1; /* colisao com o teto */ if(this.y - this.diametro <= 0) this.diry = 1; /* colisao com a barra direita */ if((this.x + this.diametro) >= playerR.x && this.y >= playerR.y && this.y <= (playerR.y + playerR.altura)) this.dirx = -1; }; Se você fez tudo corretamente já será possível experimentar o PONG que já está quase finalizado. Experimente jogar um pouco alterando os valores speed da bola ou mesmo diminuindo o valor passado ao setTimeout. Mas, lembre-se: quanto menor esse valor mais o jogo exigirá fisicamente da sua máquina. 45 7.1 Velocidade da Bola No pong padrão a medida que o jogo vai ocorrendo a bola têm sua velocidade aumentada. Para conseguir isso vamos criar mais um atributo na classe Boll. Esse atributo será chamado de mod. 1 2 3 4 function Boll(){ /*... codigos */ this.mod = 0; }; A incrementação da velocidade da bola ocorrerá por meio da soma da variável speed com a variável mod. Assim, altere as linha a seguir disso: 1 2 3 4 5 Boll.prototype.move = function(canvas){ this.cnv = canvas; this.x += this.speed*this.dirx; this.y += this.speed*this.diry; /* codigos */ para isso: 1 2 3 4 5 6 Boll.prototype.move = function(canvas){ this.cnv = canvas; this.x += (this.speed+this.mod)*this.dirx; this.y += (this.speed+this.mod)*this.diry; /* codigos */ }; Os parênteses acima são necessários pois a multiplicação de variáveis têm prioridade em cima da soma. 46 Faremos com que a bola sofra um incremento de velocidade apenas quando for rebatida por uma das barras assim, colocamos um incremento para o atributo mod dentro dos if’s de colisão da bola com as barras, veja. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Boll.prototype.move = function(canvas){ this.canvas = canvas; this.x += (this.speed+this.mod)*this.dirx; this.y += (this.speed+this.mod)*this.diry; /* ... */ /* colisao com a barra esquerda */ if((this.x - this.diametro) <= (playerL.x + playerL. largura) && this.y >= playerL.y && this.y <= playerL.y + playerR.altura){ this.dirx = 1; this.mod += 0.2; } /* ... */ /* colisao com a barra direita */ if((this.x + this.diametro) >= playerR.x && this.y >= playerR.y && this.y <= (playerR.y + playerR.altura)){ this.dirx = -1; this.mod += 0.2; } }; O valor de 0.2 é opcional. Experimente o valor que mais lhe agradar. Quanto maior mais rapidamente a bola sofrerá uma variação de velocidade. Os outros fatores que influenciam na velocidade do seu jogo é o valor inicial do atributo mod, speed do objeto bola e o valor com que a função setTimeout está chamando a função Game. 47 7.2 1 2 3 Fim de Seção 50 8 Placar Talvez você ainda não tenha percebido, mas quando a bola não é rebatida por nenhuma barra isso faz com que ela desapareça por alguns instantes e logo em seguida surja atrás das barras que usamos para rebater a bola. Vamos resolver este problema construindo um sistema de score. Primeiro criamos uma função chamada de pontuação. 1 2 3 4 5 6 7 8 9 10 11 12 function pontuacao(ponto){ this.ponto = ponto; if(this.ponto == 1) playerR.placar += 1; if(this.ponto == 2) playerL.placar += 1; /* Zerando o modificador} */ bola.mod = 0; /* Volta bola para posicao inicial */ bola.y = (canvas.height/2); bola.x = (canvas.width/2); }; E no método move do objeto Boll acrescentamos o seguinte: 1 2 3 4 if(this.x-this.diametro/2 < 0){ pontuacao(1); if(this.x+this.diametro/2 > this.cnv.width) pontuacao(2); Os dois if’s são responsáveis por chamar a função pontuacao. Se a colisão da bola ocorre coma a parede esquerda então a função pontuacao é chamada passando lhe o atributo ponto igual a um. Mas, se a colisão ocorre com a parede a direita então a função é chamada passando lhe o atributo ponto igual a dois. E quando a função ponto é chamada então ela muda o placar. Por fim mudamos o atributo placar do método move para a 51 classe Raquete. 1 2 3 4 5 6 7 8 function Raquete(x,y,larg,alt,ctx){ this.x = x; this.y = y; this.largura = larg; this.altura = alt; this.ctx = ctx; this.placar = 0; /* atributo placar */ }; Com todas essas alterações finalmente terminamos o nosso jogo. Na próxima página coloquei todo o código escrito. Olhe atentamente para que todas as alterações e incrementos feitos nesse capítulo sejam completamente entendidos. 52 8.1 Final de Seção Finalizando esta apostila apresento o código completo do projeto. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Pong com HTML5 e JavaScript \begin{lstlisting} 55 9 MELHORANDO A PERFORMASSE [Opcional] Você pode melhorar a performance do jogo usando uma função especial para a animação o requestAnimationFrame. Para usa-la fazemos o seguinte. Primeiro modificamos a função Game para o seguinte: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var anterior = new Date().getTime(); requestAnimationFrame(Game); function Game(){ var agora = new Date().getTime(); var decorrido = agora - anterior; Linha(); playerL.draw(); playerR.draw(); bola.draw(); playerL.score(50,20,1); playerR.score(440,20,2); playerR.move(5,38,40); playerL.move(5,87,83); bola.move(canvas); anterior = agora; requestAnimationFrame(Game); }; A primeira linha captura a hora atual em milissegundos. Na linha 4 capturamos novamente a data e na linha 5 calculamos a diferença entre elas. Na linha, 15 a variável anterior é atualizada para ser usada novamente quando a função Game for novamente chamada. Essa diferença que calculamos será usada para moderar a velocidade da bola. Assim, primeiro declaramos a variável decorrido no início do script. 1 var decorrido = 1000; Depois ajustamos as linhas de movimento da bola disso: 1 this.x += (this.speed + this.mod)*this.dirx; 56 2 this.y += (this.speed + this.mod)*this.diry; para isso 1 2 this.x += (this.speed *(decorrido/1000) + this.mod)*this. dirx; this.y += (this.speed *(decorrido/1000) + this.mod)*this. diry; Executando o seu script você vai notar que tanto a bola como as barras se movem mais depressa agora. Talvez seja necessário reajustar as velocidades das barras e da bola. Uma sugestão é usar as barras com velocidade igual a 3. E da bola igual a 1. Esse aumento na velocidade ocorre porque agora estamos trabalhando com cilos de animação mais curtos, porém bem mais precisos do que usando a função setTimeout. 57 10 LIVROS INDICADOS Desenvolva jogos com HTML5 Canvas e JavaScript. Éderson Cássio. 2013. W3Schools. Html5 canvas. Logica de Programação: Crie seus primeiros programas usando JavaScript e HTML. Paulo Silveira. Adriano Almeida. [email protected] Diego Oliveira 58