Você está na página 1de 220
Desenvolva jogos com HTMLS5 Canvas e JavaScript Casa do 2 Z Codigo EDERSON CASSIO Casa do Codigo Introducao Se vocé chegou até este livro, ¢ porque esti interessado em desenvolver jo- gos. Aqui focarei no desenvolvimento de jogos para a web, usando a tec nologia Canvas, presente na especificagao do HTMLs e suportada por to- dos os maiores browsers modernos, em suas versées mais atualizadas. Pre- tendo mostrar que desenvolver jogos é uma tarefa na realidade simples, e que nao exige grandes curvas de aprendizados com frameworks monstruosos — © basico oferecido pelo ambiente do navegador é 0 suficiente para comegar! Tudo o que vocé precisara é de um browser atualizado e um bom edi- tor de textos. Em ambiente Windows, é muito comum 0 uso do Notepad++ (http://notepad-plus-plus.org) . Caso vocé use Linux, é bem provavel que sua distribuigao j venha com um editor que suporte coloragao de sintaxe para varias linguagens (GEdit, KWrite, etc.) No entanto, eu recomendo veemente- mente o uso do Brackets (http://brackets.io) , que foi onde eu criei os cédigos. E multiplataforma (funciona em Windows, Linux, Mac OS X) e realiza auto- completar em cédigo JavaScript de forma bastante eficiente. O jogo que vocé vai criar ja estd disponivel na web. Vocé pode jogé-lo a qualquer momento em: http://gamecursos.com.br/livro/jogo Estarei sempre presente e atuante no seguinte grupo do Google Groups, como forma de interagir com meus leitores: http://groups.google.com/forum/#!forum/livro-jogos-htmls-canvas Por fim, todos os cédigos e imagens estio disponiveis em (embora eu aconselhe vocé a digitar os cédigos e usar os prontos no download apenas para referéncia em caso de dividas): http://github.com/EdyKnopfler/games-js/archive/master.zip Casa do Cédigo ii Bom estudo! Casa do Codigo Sumirio Sumario 1 Fundamentos 1 11 —Introdugao ao HTMLs Canvas... 1. ee ee 12 Comecandoadesenhar .. 0.0... 0.0 cee eee 6 13 Animagées com requestAnimationFrame ... 6... 0c 22 1.4 Orientagio a objetos com JavaScript... 2. eee 26 2 Oloop de animagio 35 21 Introdugioe sprites ©... eee aaus (35 2.2 Teste paraa classe Animacao............-. sees 38 23. Desenvolva a classe Animacao mn 2.4 Implemente a classe Bola . . . 45 3 Ainteracao com 0 jogador — leitura apurada do teclado 49 31 EventListeners e os eventos keydownekeyup ......... 50 3-2 Detectando se uma tecla esta ou nao pressionada ....... 55 3.3 Efetuando disparos — detectando somente o primeiro keydown 58 4 Folhas de sprites — spritesheets 69 4a Conceito eabordagem utilizada 0. 69 4.2 Catregando imagens e fazendo recortes (clipping) .... . . np 43 Animagées de sprite —a classe Spritesheet .......... 5 4.4 Controle o herdi pelo teclado e veja sua animagio ... . . « 80 Sumério Casa do Codigo 5 Detecgao de colisées 87 51 Colisio entre retangulos . 2.22... .0202020 0005 87 5.2 Teste da classe Colisor.. 2.2.2... 0c eee 90 53 AclasseColisor 2.2.0.0... eevee eee eee 93 5.4 Criando um sprite colidivel..... 0.0.0.0 00200005 95 55 Melhorando océdigo . 6... ee ee eee eee eee 98 6 Iniciando o desenvolvimento do jogo 107 61 Animagio de fundo com efeito parallax... 2... 107 62 Controle da nave na horizontal e na vertical... . . 8 63 Efetuandodisparos .. 0.2.2.2... 00-0005 123 7 Criando inimigos 129 71 Primeiro teste com nave ei 130 FE) BAe oo es sw snes ces wenn ns om 134 73 Adicionando fundo em parallax 6.00.06... cece eee 135 74 Adicionando colisio. 6... 0.02. eee 139 75 Estamos experimentando lentidao! .......... 145; 76 Excluindo os objetos desnecessérios........ 151 8 Incorpore animagées, sons, pausa e vidas extras ao jogo 155 81 Organizandoocédigo..............00-5 156 82 Animagio cronometrada.... 6.6... e eee ees 162 83 Animando anave com spritesheets. .. 02.0002... 2s 165 8&4 Criandoexplosdes 0.02.26. eee ee 169 Bis: Pannandowajago) 0... + soma e ce een suman etna 175 86 Sonsemiisicadefundo.............00-5 179 By) Weledieloading! «6... scone cee eee seam se 181 GAs TRUER aa oe es iw smn ee ces ween Om 186 Sey; Pane cs ce ees eee eee 190 B10 TeladeGame Over 20... 6.6 eee eee eee 192 iv Casa do Codigo Sumirio 9 Publique seu jogo e torne-o conhecido 197 9.1 Hospede-o em um servico gratuito . . . 197 9.2 Linkando com as redes sociais 203 Bibliografia 213 CapiTULO1 Fundamentos Aqui comega uma fascinante jornada pelos segredos de uma tecnologia que, na verdade, nao possui segredo algum. ‘Trata-se do Canvas, uma das maravil- has do HTMLs. © Canvas é uma area retangular em uma pagina web onde podemos criar desenhos programaticamente, usando JavaScript (a linguagem de pro- gramacao normal das paginas HTML). Com esta tecnologia, podemos criar trabalhos artisticos, animagGes e jogos, que é 0 assunto central deste livro. Com 0 Canvas, ao longo dos capitulos, iremos desenvolver 0 jogo da figura 1a: Casa do Cédigo Figura 11: Jogo desenvolvido ao longo do livro Este livro ¢ composto pelos seguintes capitulos: 1, Fundamentos: neste capitulo, aprenda como funciona o Canvas, como criar animages via programacao e também nogées de Orientacio a obje- tos em JavaScript, para que nossos codigos se tornem mais organizados € reaproveitaveis; 2. O loop de animagao: controle a animacao de seus jogos de forma efi- ciente. Conhega 0 conceito de sprites e aprenda a gerencié-los em uma ani- maco; 3. A interagao com o jogador — leitura apurada do teclado: saiba como capturar eventos do teclado de maneira eficiente para jogos; 4. Folhas de sprites — spritesheets: anime os elementos de seu jogo indi- vidualmente usando imagens contendo todos os quadros de uma animagao; 5. Detecgao de colisdes: aprenda a detectar quando os elementos de um jogo “tocam” uns aos outros na tela e execute as agdes adequadas; 6. Iniciando 0 desenvolvimento do jogo: um joguinho de nave comegara a tomar forma usando todos os elementos aprendidos até entao; 7. Criando inimigos: adicione verdadeira emogao ao seu jogo, dando Casa do Codigo Capitulo. Fundamentos ao heréi alguém a quem enfrentar. Usaremos intensivamente a detecgio de colisées; 8, Incorpore animagées, sons, pausa e vidas extras ao jogo: com todos os conceitos aprendidos e bem fixados, voce vera como ¢ facil estender 0 jogo e adicionar novos elementos. Ao fim do capitulo, vocé tera sugestoes de mel- horias que vocé mesmo poders tentar realizar, como exercicio; 9. Publique seu jogo e torne-0 conhecido: um passo a passo de como publicar seu jogo na web e divulgé-lo nas redes sociais. Importante: preparei um pacote de arquivos contendo todos os cédigos, imagens e sons utilizados. Em cada novo arquivo que criarmos, indicarei 0 nome do respectivo arquivo nesse pacote. Realize seu download no enderego: http://github.com/EdyKnopfler/games-js/archive/master.zip Antes de comecarmos a desenvolver um jogo em especifico, ¢ importante nos habituarmos a algumas fungées da tecnologia Canvas. O que esta es- perando? Vamos comegar 0 aprendizado! 1 INTRODUGAO AO HTMLs Canvas Para criar um Canvas em uma pagina HTML, utilizamosa tag . Os atributos width e height informam a largura e a altura, respectivamente, da drea de desenho. £ importante também informar um id para podermos trabalhar com ele no cédigo JavaScript: Entre as tags de abertura e fechamento, podemos colocar alguma men- sagem indicando que o browser nao suporta essa tecnologia. Caso 0 browser a suporte, esse contetido ¢ ignorado: Seu navegador n&o suporta o Canvas do HTHLS.
Procure atualiza-lo.
Os atributos width e height da tag sao obrigatérios, pois sio os valores usados na geracao da imagem. © Canvas pode receber dimen- 1a, Introdugie ao HTML Canvas Casa do Cédigo sdes diferentes via CSS, no entanto, seu processamento sempre serd feito us- ando as dimensdes informadas na tag. Se as dimensdes no CSS forem difer- entes, o browser amplia ou reduz a imagem gerada para deixé-la de acordo com a folha de estilo. Dado um Canvas com dimensées 100x100 pixels: au_canvas" width="100" heigh’ A seguinte formatagéo CSS far a imagem ser ampliada: #meu_canvas { width: 200px; height: 200px; Contexto grafico Para desenhar no Canvas, é preciso executar um script apés ele ter sido carregado. Neste script, obteremos 0 contexto grdfico, que é 0 objeto que re- aliza de fato as tarefas de desenho no Canvas. Uma maneira é criar uma tag Casa do Codigo Capitulo. Fundamentos Também é muito comum desenharmos em eventos que ocorrem apés a pagina ter sido carregada. Isto é util caso queiramos colocar os scripts na segio do documento HTML: Processando o Canvas na sega HEAD "meu_canvas" width="200" hei ght=""200"> No cédigo, nés referenciamos 0 Canvas e obtemos o contexto grafico. O Canvas é referenciado como qualquer elemento em uma pagina; o contexto éobtido pelo método get context do Canvas. Como parimetro, passamos uma string identificando 0 contexto desejado. Neste livro, usaremos 0 con- texto 2a (“d” em mintisculo!): 12, Comegando a desenhar Casa do Cédigo O sistema de coordenadas do Canvas Para posicionarmos os desenhos no Canvas, pensamos nele como um enorme conjunto de pontos. Cada ponto possui uma posigao horizontal (x) uma vertical (y). O ponto (0, 0) (Ié-se: zero em x € zero em_y) corresponde ao canto superior esquerdo do Canvas: (0, 0) y Figura 1.2: Sistema de coordenadas do Canvas 1.2 COMEGANDO A DESENHAR Métodos fillRect e strokeRect Uma vez obtido 0 contexto grafico, podemos configurar varias propriedades e chamar nele os métodos de desenho. Por exemplo, para desenhar retangulos, podemos usar os métodos « fillRect (x, y, largura, altura): pintacompletamente uma rea retangular; + strokeRect (x, y, larqura, altura): desenha um contorno do retingulo. Os valores x ey corresponderao & posicdo do canto superior esquerdo do retingulo. A partir dai, o retingulo vai para a direita (largura) e para baixo (altura). Casa do Codigo Capitulo. Fundamentos Veja um exemplo de cédigo para desenhar um retingulo no Canvas, nosso primeiro exemplo pritico completo: arquivo: retangulos-1.html --> este cédigo vai dentro do body --> 100" > / Canvas e contexto var canvas - document. getElementByTd('meu_canvas') ; var context = canvas. getContext ('24'); // Desenhar um retangulo context. fillRect(50, 50, 100, 100); Nao é simples de fazer? O resultado sera um simples retangulo preto. Seu canto superior esquerdo localiza-se no ponto (50, 50), ¢ ele possui 100 pixels de largura por 100 pixels de altura: Figura 1.3: Retangulo desenhado com fillRect Se trocarmos f111Rect por strokeRect, veremos apenas 0 contorno: Figura 1.4: Retangulo desenhado com strokeRect 12, Comegando a desenhar Casa do Cédigo Propriedades fillStyle, strokeStyle ¢ line Width Podemos configurar algumas propriedades do contexto, de forma a escol- her as cores e espessuras: + fillStyle: cor do preenchimento + strokeStyle: cor da linha + lineWidth: espessura da linha em pixels eu_canvas" widt! arquivo: // Canvas e contexto var canvas = document. getElementById('meu_canvas'); var context = canvas. getContext('2d'); 300" height="300"> // Cores e espessura da linha context. fillStyle = ‘gray’; 12 Casa do Codigo Capitulo. Fundamentos contert.strokeStyle - ‘black’; context. linewidth = // continua... Para fazer um arco, devemos iniciar um path, chamar 0 método arc e depois fazer £111 stroke (ou somente um destes dois, se quisermos somente preenchimento ou somente contorno): // Primeiro arco: // Novo path context. beginPath() ; // Desenha context.arc(50, 50, 40, 90*Nath.PI/180, 270sMath.PI/180, false); // Preenchimento context. fi11(); // Contorno context. stroke(); // continua... Se quisermos fazer outro arco, temos que iniciar mais um path; do con- trdrio, 0 desenho formado ficard estranho (figura 1.8). Repare que a posigao x foi deslocada para desenharmos a direita do anterior: // Segundo arco context. beginPath(); context.arc(150, 50, 40, 90*Math.PI/180, 270+Math.PI/180, true); context. fill(); context. stroke(); // continua ... 3 1a. Comesando a desenhar Casa do Cédigo Figura 1. do outro! Dois arcos desenhados juntos no mesmo path: um é continuagio Faga também uma circunferéncia completa. Nao precisa se preocupar em converter 360 graus para radianos, basta fazer © arco de zero até 2 pi. Vamos omitir o iiltimo parametro, pois tanto faz fazermos no sentido hordrio ou anti-hordrio (j4 que vamos comegar e finalizar no mesmo ponto): // Circunferéncia completa context. beginPath() 5 context.arc(250, 50, 40, 0, 24Nath.PI); context. fi11(); context. stroke(); Pronto! Temos os seguintes desenhos na pagina: Figura 1.9: Arco em sentido anti-horério, arco em sentido horério e circun- feréncia completa, com preenchimento e contorno Desenhando imagens Se voce jé esta ficando preocupado, pensando se todos os graficos em um jogo sao criados via programacio, eu tenho uma boa noticia para vocé: nao 4 Casa do Codigo Capitulo. Fundamentos sio. A grande maioria vem de imagens jé prontas, elaboradas em programas graficos. Para desenhar imagens pré-elaboradas em um Canvas, primeiro temos de carregar 0 arquivo de imagem. O objeto Tmage do JavaScript ¢ equivalente a um elemento na pagina, porém somente em meméria. Apés crid-lo, apontamos seu atributo sxc para o arquivo desejado: // Exemplo teérico // Carregando uma imagem programa var imagem = new Image(); ‘minha-imagem.png amente imagem. src // git, jpg. // Obtendo de uma imagem na pagina var imagem = document. getElementById('id_da_tag_img'); Convém aguardar a imagem ser carregada antes de desenhd-la. O objeto Image possui o evento onload, que sera disparado automaticamente pelo browser quando o carregamento estiver completo: // Exemplo teérico imagem.onload = function() { // Aqui trabalhamos com a imagen Estando carregada, a imagem pode ser desenhada através do método drawImage do context. Este método pode ser chamado de duas formas: « drawImage(imagem, x, y, largura, altura): desenhaaim- agem inteira, na posicao e tamanho especificados; « drawImage(imagem, xOrigem, yOrigem, larguraOrigem, alturaOrigem, xDestino, yDestino, larguraDestino, alturaDest ino): desenha parte da imagem. Vamos experimentar a primeira forma. No pacote de download, na pasta deste capitulo ( 01), esta presente uma subpasta de nome img, contendo © arquivo ovni.png. Este serd posteriormente o inimigo que a nave ira 15 1a, Comesando a desenhar Casa do Cédigo enfrentar em nosso jogo. A imagem possui 64 pixels de largura por 32 de altura (64x32). Vamos criar uma pagina com um Canvas e carregé-la: "500" heigh No evento onload, fazemos um loop para desenhar cinco OVNIs, um ao lado do outro. A variavel x indica a posi¢ao de cada desenho: imagem.onload = function() { // Comegar na posig&o 20 var x = 20; // Desenhar as imagens for (var i = 13 i <= 5; i++) { context.drawIlmage(imagem, x, 20, 64, 32); x #= 703 CEP EP ap ap ai Figura 1.10: Desenhando imagens com drawlmage 16 1a do Codigo Capitulo. Fundamentos Se quiséssemos desenhar as imagens ampliadas ou reduzidas, bastaria modificar a largura ea altura: // Reduzindo para metade do tamanho context.drawImage (imagem, x, 20, 32, 16); // Ampliando para o dobro do tamanho context. drawImage(imagem, x, 20, 128, 64); Vamos experimentar a segunda forma, a que recebe oito valores! Na pasta img, temos 0 arquivo explosao.png, que também sera usado no jogo. Esta imagem contém uma sequéncia de animagio (spritesheet), da qual de- senhamos uma parte por vez: vv ve = Q & .¢- > * Figura 1.11: Explosao sada no jogo do livro A técnica de clipping consite em selecionar uma 4rea da imagem original para ser desenhada: v v ™ & «- * . elecionando uma drea da imagem para recorte (clipping) Figura 1.12: Os quatro primeiros valores passados a0 drawImage indicam 0 retan- gulo da area enquadrada; os outros quatro representam a posic¢ao eo tamanho do desenho no Canvas: y 12, Comegando a desenhar Casa do Cédigo arquivo: imagens-2.html --> '300"> // Canvas e contexto var canvas = document.getElementById('meu_canvas') var context = canvas. getContext('24") ; // Carregar a imagem var imagem = new Image(); imagem.sr¢ = ' img/explosao.png'; imagem.onload = function() { context. drawImage( imagem, 80, 10, 60, 65, // Area de recorte (clipping) 20, 20, 60, 65 // Desenho no Canvas + Figura 1.13: Resultado do clipping no Canvas No capitulo 4, faremos uso intensivo do clipping para criar animagées. Métodos save e restore Um recurso do context que considero de extrema importancia é poder facilmente guardar configuragées e retornar a elas mais tarde. F uma exce- lente pratica que cada fungdo ou método guarde as configura¢des atuais e as restaure antes de retornar. Desta forma, um no afeta o trabalho do outro! Mas jd imaginou ter que salvar cada propriedade em uma varivel? 18 Casa do Codigo Capitulo. Fundamentos // Exemplo teérico // Wao faga isto! function desenharQualquerCoisa() { // Guardo as configuragdes var fillanterior = context.fillstyle; // strokeStyle, ete // Modifico conforme preciso context.fillstyle = 'pink'; // Desenho WW // Devolvo as configuragdes anteriores context.fillstyle = fillanterior; Para nossa sorte, 0 context possuio método save. Este método em- pilha configuragées, fazendo uma cépia de todas as configuragées atuais para © préximo nivel. Pense em cada nivel como um “andar” nessa pilha. Nés sem- pre trabalhamos no nivel mais alto, de modo que configuragdes que ficaram em niveis inferiores permanecem inalteradas: configuragSes atuais configuragées configuracées. atuais anteriores configuragoes configuragdes configuracdes atuals save() anteriores || Save() ‘anteriores Figura 1.14: Método save empilhando configuragées Quando queremos retornar as configuragées anteriores, chamamos 0 método restore. Ele volta para o nivel imediatamente abaixo: 19 12, Comegando a desenhar Casa do Cédigo configuragoes atuais configuragdes ‘conkiguragses. ‘anteriores thee configuragdes configuragées configuragses ‘anteriores. | [ restored) pamgurasbes | T estaeg ee Figura 1.15: Voltando para configuragées anteriores com restore Como exemplo pratico, comegamos desenhando um quadrado verde. Em seguida, subimos na pilha com save e fazemos um quadrado roxo. Depois que retornamos com restore, nossa cor atual passa a ser 0 verde nova- mente: 200" height '200"> // Canvas e contexte var canvas = document.getElementById('meu_canvas'); eu_canvas" widt! var context = canvas.getContext('2d'); // Um pequeno quadrado verde context.fillstyle = 'green'; context.fillRect(50, 50, 25, 25); // Salvamos a configuragéo e subimos na pilha context. save(); // Agora um quadrado roxo context. fillstyle = 'purple'; context. fillRect(100, 50, 25, 25); // Yoltamos para o nivel anterior na pilha context. restore(); // Yoltou para o verde! 20 Casa do Codigo Capitulo. Fundamentos context. fillRect(150, 50, 25, 25); Figura 1.16: Salvamos a cor verde na pilha (save), mudamos para roxo e volta- mos para o verde (restore) REFERENCIAS SOBRE 0 CANVAS Neste t6pico, nao quis nem de longe esgotar os recursos do Canvas, mas dar uma base para iniciarmos 0 desenvolvimento de jogos. Indico- Ihe algumas referéncias para vocé aprimorar seus conhecimentos e ter ideias, muitas ideias, para implementar em seus jogos: + W3Schools[3]_(http://www.w3schools.com/tags/ref_canvas.asp) : recomendo todos os tutoriais deles, por serem simples e por vocé poder rodar os exemplos na hora; + Core HTMLs Canvas(1] (http://corehtmlscanvas.com) : excelente livro de David Geary, bem aprofundado, foi a minha principal fonte para aprender sobre o Canvas. © autor discute nao somente os recursos da ferramenta, mas técnicas e algoritmos extrema- mente avangados e uteis para jogos; + HTMLs 2D game development: Introducing Snail Bait(2] (http: //goo.glOjvigT - URL encurtada): do mesmo autor do Core HTMLs Canvas, este é o primeiro de uma série de artigos na qual somos guiados na criagio de um jogo de plataforma. 2 1.3, Animagies com requestAnimationEname Casa do Cédigo 1.3 ANIMAGOES COM REQUESTANIMATIONFRAME Tradicionalmente, quando queremos executar tarefas_periédicas em pdginas web, usamos os métodos window.setTimeout ou window.setInterval, passando a fungao desejada e o intervalo em milissegundos. Os resultados sao satisfatérios para tarefas simples, no entanto, o controle do tempo pelo browser nao é totalmente preciso. E preciso lembrar que os sistemas operacionais modernos sao multitarefa, ¢ mesmo os browsers podem ter varias guias abertas. Nao é possivel garantir que a CPU esteja sempre disponivel no momento exato desejado, portanto 0 intervalo informado sempre ser aproximado. A solugao para jogos e outras aplicagées que requerem animagées mais precisas ¢ trabalhar com ciclos mais curtos, aproveitando cada momento em que a CPU esta disponivel para nosso aplicativo, e nesse momento fazer 0 calculo do tempo. Para isso, a especificagio do HTMLs traz 0 método window. requestAnimat ionFrame, que delega para o browser a tarefa de executar sua animagao o mais rapido possivel, assim que os recursos do sis tema estiverem disponiveis. Para este método, passamos como parametro a fungao que fara os desenhos no Canvas. Temos que chama-lo de forma ciclica, uma vez apés a outra, da mesma forma que farfamos caso uséssemos 0 con- hecido set Timeout: // Exemplo teérico // Solicito a execugao de uma fungao // N&o @ preciso qualificar o "window" :D request AnimationFrame(minhaFuncao) ; // Fungéo de animagéo function minhaFuncao() { // Fago um desenho qualquer “ // Solicito o préximo ciclo request Animat ionFrame(minhaFuncao) ; Para demonstrar na pratica, fagamos uma bolinha se deslocando pela tela. 22 Casa do Codigo Capitulo. Fundamentos Obtemos as referéncias do Canvas e do contexto grifico, definimos a posicio inicial da bola e seu raio e, em seguida, mandamos uma animagio iniciar, chamando a fungie mexerBola: // Canvas e contexto canvas = document.getElementById('meu_canvas'); context = canvas. getContext('2d'); '300"> // Dados da bola var x = 20; var y = 1003 var raio // Iniciar a animagao request Animat ionFrame(mexerBola) ; // Fung&o de animagao function mexerBola() { // Aqui uma bolinha se deslocara } Nessa fungio, primeiro limpamos 0 Canvas com 0 método clearkect do contexto, que serve para limpar uma drea (equivale a desenhar um retan- gulo branco). Fazemos isso para apagar rastros anteriores da bolinha. Em seguida, nds a desenhamos em sua posicao atual, alteramos sua posigao x € solicitamos a execucao do préximo ciclo de animagao: function mexerBola() { // Limpar o Canvas context.clearRect(0, 0, canvas.width, canvas. height); // Desenhar a bola context. beginPath() ; context.arc(x, y, Taio, 0, Math.PI#2); context. f1110)5 23 1.3, Animagies com reguestAnimationEname Casa do Cédigo // Deslocar 20 pixels para a direita x += 20; // Chamar © préximo ciclo da animagao request AnimationFrame(mexerBola) ; Faca experiéncias! Vocé pode: + mexer para a direita: some um valor ax (veja a figura 1.2); + mexer para a esquerda; subtraia um valor de x; + mexer para baixo: some um valor a y; + mexer para cima: subtraia um valor de ys + mexer na diagonal: altere as posicdes tanto de x quanto de y. Vocé pode somar ou subtrair, usar valores diferentes para cada um ete. Controlando o tempo da animacao Repare que a bolinha anda bem depressa. Isto ocorre porque o requestAn ationFrame trabalha com ciclos curtos, aproveitando 0 primeiro momento em que a CPU e o browser puderem executar 0 processa~ mento da fungao de animagio. Podemos ler 0 relégio do computador em cada ciclo para controlar o movimento da bolinha. Sabemos que o JavaScript possui o objeto Date, que obtém a data ea hora atuais, e que esse objeto possui o método get'Time (), que devolve esse instante exato em milissegundos. Para saber quanto tempo demorou entre um ciclo e outro (lembre-se de que esse tempo é sempre var- idvel), bastaria tirar a diferenca entre o instante atual ¢ o anterior. Sabendo o intervalo decorrido, é possivel calcular quanto a bola deve se deslocar nesse tempo: // Exemplo teérico // Obter o instante atual 24 Casa do Codigo Capitulo. Fundamentos var agora = new Date().getTime(); // 0 instante anterior tem de ter sido preservado anteriormente var anterior = agora; // Foi feito antes // Tempo decorrido = diferenga var decorrido = agora - anterior; // Deslocamento da bolinha var velocidade = 20; // Em pixels por segundo x += velocidade * decorride / 1000; Vamos colocar isto em pritica. Primeiro, guarde 0 momento inicial antes de chamar a fungao de animagio pela primeira vez: // Momento inicial var anterior = new Date().getTime(); // Iniciar a animagéo request Animat ionFrame(mexerBola) ; // Fung&o de animagao function mexerBola() { Mt No inicio da fungao, obtenha o instante atual e calcule o tempo decorrido entre eles: function mexerBola() { // Momento atual var agora = new Date().getTime(); // Diferenga var decorrido = agora - anterior; Mt Ao final da funcao, antes de chamar o préximo ciclo, guarde o momento do ciclo anterior: 25 1.4. Orientagao a objetos com JavaScript Casa do Cédigo function mexerBola() { Mo. // Guardamos o instante para o préximo ciclo anterior = agora; request AnimationFrame(mexerBola) ; Agora podemos calcular 0 desclocamento da bola! Apague a linha que aumenta x em 20 pixels: // kpague esta linha // Deslocar 20 pixels para a direita x += 20; No lugar, calcule o deslocamento a partir da velocidade de 20 pixels por segundo (ou outra que preferir): // Deslocar 20 pixels por segundo var velocidade = 20; x #= velocidade * decorrido / 1000; A bola agora move-se mais lentamente, pois agora estamos trabalhando com pixels por segundo (antes eram 20 pixels por ciclo, e tinhamos intimeros ciclos em um segundo). Para fazé-la mover-se mais rapido, basta aumentar 0 valor. 1.4 ORIENTAGAO A OBJETOS COM JAVASCRIPT Conceitos basicos Nao é intengao deste livro ensinar a teoria da orientagdo a objetos (0.0.), mas vou dar-lhe uma base para podermos usé-la com JavaScript, Esta nao uma linguagem puramente O.0., no entanto, o jogo que criaremos, esta projetado dessa forma. Diferenciamos aqui projeto orientado a objetos (apli- cado em nosso jogo) de linguagem orientada a objetos, que da suporte total ao paradigma (nao é 0 caso do JavaScript). 26 Casa do Codigo Capitulo. Fundamentos Imagine um jogo de carros de corrida. Todos os carros sio padronizados nas suas caracteristicas, embora elas possam variar (dentro desse padrao). Por exemplo, todos os carros possuem cor, velocidade maxima e velocidade atual. Contudo, em um dado momento, cada carro possui valores diferentes para estas caracteristicas. © carro do jogador é vermelho, esta a 150km/h e pode alcangar até 200km/h. Seu rival é azul, esté a 1yokm/h e pode alcancar até 220km/h. As caracteristicas que definem um carro no jogo sao as mesmas, mas cada uma est4 preenchida com valores diferentes para cada carro. Esse conjunto de caracteristicas comuns a todo carro é 0 que chamamos de classe. Quando falamos carro, referindo-nos a carros em geral, estamos falando de uma classe. A classe é 0 molde para a criacao de objetos. Cada carro é uma instancia da classe. A instancia é 0 objeto em si, um objeto que pertence a uma classe. Quando falo do meu carro, estou falando de uma instdncia especifica da classe carro. O carro do vizinho € outra instancia. Cor, velocidade atual e velocidade maxima sao os atributos da classe, ou seja, todas as instancias os tém, mas cada uma possui valores préprios para esses atributos. Cada carro executa as tarefas de acelerar, frear e virar. Estas tarefas sao os iétodos. Em programas orientados a objeto, ¢ nos métodos que escrevemos 0 cédigos que realizam as tarefas fundamentais do programa. Eles trabalham com os dados nos atributos e podem alterar 0 estado do objeto. RESUMO DE CONCEITOS BASICOS DE 0.0. Classe: um tipo de objetos determinado, composto por atributos e métodos definidos; Instancia: cada objeto pertencente a uma determinada classe; Atributo: cada propriedade dos objetos listada em uma classes Método: cada tarefa que um objeto de uma classe pode executar. 27 1.4. Orientagao a objetos com JavaScript Casa do Cédigo Funcées construtoras Em linguagens O.0., como Java e C#, definimos classes explicitamente (usando a palavra-chave class). Em JavaScript ndo ha classes, mas pode- mos usar funcdes construtoras para criar os objetos seguindo as classes que projetamos. Uma funcdo construtora é uma fungao normal do JavaScript, porém us- ada com o propésito de criar objetos. No exemplo a seguir, a palavra-chave this recebe os atributos de um carro. A palavra cor, sozinha, se refere ao parametro recebido pela fungio, mas a construcio this. cor cria um atrib- uto cor no objeto: Movimentando personagem com classe JavaScript O JavaScript contera partes bem parecidas com o do exemplo anterior, mas introduzimos aqui um loop de animagao. F ele que vai avaliar constan- temente o estado de determinadas teclas, Primeiro referenciamos o Canvas normalmente: var canvas = document. getElementById('canvas_teclado_2'); var context = canvas.getContext ('2d'); Em seguida, definimos a posicao inicial do personagem e mandamos de- senhar: var posicao desenharPersonagem() 5 Agora vamos criar 0 controlador do teclado. Quero passar para ele o ele- mento da pagina onde os eventos sero ouvidos (no caso, document): var teclado = new Teclado(document) ; O loop de animacao ser4 bem basico, por enquanto através de uma sim- ples fungao. Nesta fungio, primeiro lemos o estado das teclas que queremos mudamos a posi¢ao do personagem de acordo. Em seguida, nds o desen- hamos e chamamos o préximo ciclo: request AnimationFrame(animar) ; function animar() { if (teclado. pressionada(SETA_ESQUERDA)) Posicao -= 10; else if (teclado. pressionada(SETA_DIREITA)) posicao += 10; desenharPersonagem() ; request AnimationFrame(animar) ; 56 Casa do Codigo Capitulo 3. A interasio com o jogaclor — leitura apurada do teclado A fungio desenharPersonagem é a mesma criada anteriormente: // Nosso heréi continua esbanjando charme! :D function desenharPersonagem() { context.clearRect(0, 0, canvas.width, canvas.height) ; context. fillRect(posicao, 100, 20, 50); Aclasse Teclado Esta classe recebe um elemento qualquer da pagina no construtor e atribui a ele os eventos que vao registrar o estado das teclas em um array. O método pressicnada devolve o valor true caso a tecla conste como pressionada no momento: // arquivo: teclado. js // Cédigos de teclas - aqui v&o todos os que forem necessérios var SETA_ESQUERDA = 37; var SETA_DIREITA = 395 function Teclado(elemento) { this.elemento = elemento; // krray de teclas pressionadas this.pressionadas = []; // Registrando o estado das teclas no array var teclado = this; elemento. addEventListener('keydown', function(evento) { teclado. pressionadas [evento. keyCode] = true} Ys elemento. addEventListener('keyup', function(evento) { teclado. pressionadas evento. keyCode] = false; + Teclado. prototype = { pressionada: function(tecla) { return this.pressionadas[teclal ; + } 57 533. Bfetuando disparos — detectando somente o primeiro keydown Casa do Cédigo Abra a pagina e veja que o “personagem” move-se com muito mais suavi- dade e fluéncia! 3.3 EFETUANDO DISPAROS — DETECTANDO SOMENTE O PRIMEIRO KEYDOWN ‘Vamos dar um superpoder ao heréi: atirar. Neste ponto, para facilitar a nossa vida, vamos usar a classe Animacao e 0 conceito de sprites aprendidos ante- riormente. Que tal poder mandar o heréi atirar da forma mostrada a seguir? Se a classe Teclado sabe se uma tecla esta pressionada, ela também pode saber disparar uma acao tinica no momento do pressionamento! // Exemplo tedrico teclado.disparou(ESPACO, function() { heroi.atirar(); Hs [Um herbique airs Figura 3.5: Heréi atirando Vamos criar uma nova pagina. Os scripts bola. js animacao. js sao os mesmos jd criados. Certifique-se de ter uma cépia deles na pasta deste teste (ou linka-los corretamente em seu computador) 58 Casa do Codigo Capitulo 3. A interasio com o jogaclor — leitura apurada do teclado arquivo: teste-teclado-3.html --> Um heréi que atira Veja como o script do teste ¢ simples e conciso. Nesta altura, eu espero sinceramente que vocé esteja entendendo 0 porqué de estarmos fazendo dessa forma. Criar uma nova ago, um novo personagem, um novo inimigo devera ser cada vez mais simples a partir de agora: var canvas = document. getElementById('canvas_teclado_3!); var context = canvas.getContext('2d'); var teclado = new Teclado(document) ; var animacao = new Animacao(context) ; // Um sprite pode ler o teclado para saber // como se comportar var heroi = new Heroi(context, teclado); heroi.x = 0; heroi.y = 100; animacao. novoSprite(heroi); 59 533. Bfetuando disparos — detectando somente o primeiro keydown Casa do Cédigo teclado.disparou(ESPACO, function() { heroi.atirar(); vs animacao. ligar(); Precisamos criar 0 método disparou na classe Teclado. Para que ele funcione corretamente, é preciso registrar que determinada tecla foi dis- parada (para que os eventos keydown subsequentes nao provoquem o disparo novamente). Criamos mais dois arrays, um para registrar os disparos e outro para guardar as fung6es a serem executadas: // Cédigos de teclas - aqui v&o todos os que forem necessarios var SETA_ESQUERDA = 37; var SETA_DIREITA = 39; var ESPACO = 32; function Teclado(elemento) { this.elemento = elemento; // krray de teclas pressionadas this.pressionadas = [1]; // Avvay de teclas disparadas this.disparadas - []; // Fungées de disparo this. funcoesDisparo = []; // continua. . No evento keydown, verificamos se a tecla pressionada possui uma fungo de disparo associada e se seu disparo jd foi processado. A instrucao this.funcoesDisparo[tecla] () ; executaa funcéo guardada no ar- ray devido aos parénteses no final: var teclado = this; 60 1a do Codigo Capitulo 3. A interasio com o jogaclor — leitura apurada do teclado elemento. addEventListener('keydown', fumction(evento) { var tecla = evento.keyCode; // Tornando mais legivel ;) teclado.pressionadas[tecla] = true; // Disparar somente se for o primeiro keydown da tecla if (teclado.funcoesDisparo[tecla] at ! teclado.disparadas[tecla]) { teclado.disparadas[tecla] = true; teclado.funcoesDisparo[tecla] () ; } Ds No evento keyup setamos 0 estado “disparada” para false, de modoa tornar possiveis novos disparos: elemento. addEventListener('keyup', function(evento) { teclado. pressionadas [evento.keyCode] = false; teclado. disparadas [evento.keyCode] = false; Ys Enfim, 0 método disparou necessita apenas guardar uma fungao de disparo para uma tecla. O evento keydown esta cuidando de tudo! disparou: function(tecla, callback) { this. funcoesDisparo[tecla] - callback; Em caso de dtividas, veja 0 novo cédigo completo da classe Teclado: // arquivo teclado.js - versao final // Cédigos de teclas - aqui vAo todos os que forem necessérios var SETA_ESQUERDA = 37; var SETA_DIREITA = 395 var ESPACO = 325 function Teclado(elemento) { this.elemento = elemento; // krvay de teclas pressionadas 61 533. Bfetuando disparos — detectando somente o primeiro keydown Casa do Cédigo this.pressionadas = []; // krray de teclas disparadas this.disparadas = []; // Fungdes de disparo this. funcoesDisparo = []; var teclado = this; elemento. addEventListener('keydowm', fumction(evento) { var tecla = evento.keyCode; // Tornando mais legivel teclado. pressionadas[tecla] = true; // Disparar somente se for o primeiro keydown da tecla if (teclado.funcoesDisparo[tecla] at ! teclado.disparadas[tecla]) { teclado.disparadas[tecla] = true; teclado.funcoesDisparo[tecla] () ; } Hs elemento. addEventListener('keyup', function(evento) { teclado. pressionadas[evento. keyCode] = false; teclado. disparadas [evento.keyCode] = false; Hs 2 Teclado. prototype = { pressionada: function(tecla) { return this.pressionadas[tecla] ; 3, disparou: function(tecla, callback) { this. funcoesDisparo[tecla] = callback; E bastante abstrato, mas se conseguirmos programar de maneira mais geral agora, teremos um game engine bastante robusto e pritico para criar 62 Casa do Codigo Capitulo 3. A interasio com o jogador —leitura apurada do teclada novos jogos de maneira extremamente veloz! Crie a classe Heroi como um sprite Para nosso cédigo funcionar, temos que criar o sprite do heréi. Como © sprite é responsavel pelo seu proprio comportamento, ele deve receber 0 objeto que controla o teclado para poder decidir o que vai fazer em cada ciclo da animacio. O heréi também deve ter 0 método at irar, chamado no teste. // arquivo: heroi.js function Heroi(context, teclado) { this.context = context; this.teclado = teclado; this.x = 0; this.y = 05 Ms Heroi.prototype = { atualizar: function() { Ey desenhar: function() { 3, atirar: function() { } + No método atualizar, lemoso estado do teclado e movemos o person- agem de acordo. ‘Também nao custa nada impedir que ultrapasse as bordas da tel atualizar: function() { if (this. teclado.pressionada(SETA_ESQUERDA) && this.x > 0) this.x -= 10; else if (this.teclado.pressionada(SETA_DIREITA) && this.x < this.context.canvas.width - 20) this.x += 103 }, 63 33. Hfetuando disparos — detectando somente 0 primeiro keydown Casa do Cédigo No método desenhar... bem, eu vou continuar mantendo as coisas simples e fazer um simples retingulo. No capitulo sobre spritesheets (4) a coisa vai ficar mais interessante, prometo! desenhar: function() { this. context.fillRect(this.x, this.y, 20, 50); }, Agora, a parte mais interessante: fazé-lo atirar. Neste ponto, devemos ler novamente o estado do teclado para saber para que lado 0 tiro vai. Vamos aproveitar a classe Bola que jé temos: atirar: function® { var tiro = new Bola(this. context); tiro.x = this.x + 10; tiro.y = this.y + 10; tiro.raio = 2; tiro.cor = ‘red’; if (this. teclado. pressionada (SETA_ESQUERDA)) tiro.velocidadeX = -20; else tiro.velocidadeX = 20; // N&o tenho como incluir nada na animagio! this. animacao. novoSprite(tiro) ; Como o herdi vai acrescentar um tiro 4 animagao sem uma referéncia a ela? Vamos recebé-la também no construtor: function Heroi(context, teclado, animacao) { this. context = context; this.teclado = teclado; this.animacao = animacao; this.x = 05 this.y = 05 E passé-la no aplicativo de teste: 64 Casa do Codigo Capitulo 3. A interasio com o jogaclor — leitura apurada do teclado var heroi = new Heroi(context, teclado, animacao); Faga o teste. Se vocé fez tudo correto, o heréi deve atirar ao teclarmos espage. Mas ainda temos dois pequenos problemas: 1) a classe Bola esta programada para quicar, portanto nao vemos os tiros sumirem da tela (figura 3.6); 2) quando parado, o herdi atira sempre para a direita. [Cy Um herat que atirs Figura 3.6: Os tiros ficam quicando na tela, ao invés de sumirem_ Para resolver o primeiro problema, vamos mudar 0 algoritmo do método atualizar daclasse Bola. Recomendo que este teste referencie sua propria cépia do arquivo bola. 4s, para nao alterar 0 comportamento dos testes anteriores. atualizar: function() { // Tixamos os testes que quicam a bola na borda do Canvas this. velocidadex; = this. velocidadeY; this.x this.y 3, 65 33. Hfetuando disparos — detectando somente 0 primeiro keydown Casa do Cédigo Quanto A diregio do tiro, criamos no Hero um atributo que guarda a sua diregao: // Cédigos imicos para as diregées var DIRECAO_ESQUERDA = 1; var DIRECAO_DIREITA function Heroi(context, teclado, animacao) { this.context = context; this.teclado = teclado; this.animacao = animacao; this.x = 0; this.y = 05 // Direg&o padréo this.direcao = DIRECAO_DIREITA; Desta forma, ao movimentar o her encontra nesse atributo: , guardamos a diresao em que ele se atualizar: function() { if (this. teclado. pressionada (SETA_ESQUERDA) th this.x > 0) { this.direcao = DIRECAO_ESQUERDA; this.x -= 10; } else if (this.teclado.pressionada(SETA_DIREITA) w& this.x < this.context.canvas.width - 20) { this.direcao = DIRECAO_DIREITA; this.x += 10; 3, Por fim, basta ler essa direcao ao soltar 0 tiro, em vez do estado de deter- minada tecla: atirar: function() { Mf 66 Casa do Codigo Capitulo 3. A interasio com o jogador —leitura apurada do teclada // Lendo a diregdo atual if (this.direcao -- DIRECAO_ESQUERDA) tiro.velocidadeX = -2 else tiro.velocidadeX = 20; Mt Pronto! Temos um heréi que se movimenta ¢ atira dentro de um loop de animagio. Embora ainda esteja muito basico, ¢ preciso que os conceitos aprendidos fiquem bem fixados para seguirmos para as proximas etapas. Pro- cure reler e refazer os cddigos de cada capitulo diversas vezes. Tente tam- bém fazer algumas coisas diferentes, como mover ou atirar na vertical! Para isso, os cédigos de teclas so 38 (acima) e 40 (abaixo), e as alteragdes na posi¢ao y dos sprites serao as mesmas ja feitas para x, somente incremen- tando a partir da velocidadey. Experimentel No préximo capitulo, introduzirei as folhas de sprites (spritesheets), que nos permitirao animar nosso heréi a partir de imagens. 7 CapiruLo 4 Folhas de sprites — spritesheets 4.1. CONCEITO E ABORDAGEM UTILIZADA Um pequeno game engine estd comecando a tomar forma. Ja temos um modo robusto de controlar o loop de animagao e capturar a entrada do usuario no teclado de uma maneira eficiente para jogos. Neste capitulo, avangaremos um pouco além e faremos algo muito inter- essante e fundamental: as spritesheets (folhas de sprites). Um sprite, como yocé aprendeu no capitulo 2.1, é cada elemento controlado pelo loop de an- imagao (0 heréi, um inimigo, um bloco ou plataforma etc.). Uma folha de sprites é uma imagem contendo varias partes de uma animagio. Essas partes so alternadas constantemente para produzir 0 movimento de um ou mais sprites. 444. Conceito e abordagem utilizada Casa do Cédigo if RAPT PRAT ERADE LAD Figura 4.1: Folha de sprites (spritesheet) B uma boa pratica carregar imagens maiores contendo uma porgio de outras menores, em vez de ter cada pequena imagem em seu proprio arquivo. Lembre-se de que, em ambiente web, cada imagem requer nova conexao do browser com o servidor. Programar animagoes com spritesheets pode ser tao simples ou tao tra- balhoso quanto vocé desejar ou precisar, dependendo da abordagem uti- lizada. No livro Core HTMLs Canvas[1], de David Geary, as posigdes de cada figurinha (x, y, largura e altura) dentro da spritesheet so chumbadas em grandes arrays: var sprite = [ [0, 71, 90, 80], [40, 71, 80, 85], ... ]; // Continua. Essa abordagem é bastante econdmica em termos do espaso ocupado pelas imagens, mas gera muito trabalho extra para coletar a posicio de cada imagem e, principalmente, para enquadré-las adequadamente. Aqui, adotaremos uma abordagem bastante sistematizada: + cada spritesheet sera dividida em espacos iguais, por isso as imagens devem estar corretamente distribuidas; + cada linha da spritesheet corresponderd a um estado do sprite (ex.: parado, correndo para a direita, correndo para a esquerda); + aanimagio de um estado usard somente as figuras na sua linha prépria. 70 Casa do Codigo Capitulo 4. Fothas de sprites — spritesheets Isso gera grande quantidade de espaco vazio na imagem, aumentando o tamanho do arquivo, mas certamente facilitara muito a programacio. Tenha em mente que esta no é a nica abordagem valida, mas para fins didaticos 6a que iremos utilizar V convenhamos, o modo mais econémico descrito no livro Core HTMLs Canvas é totalmente improdutivo, em especial quando 0 foco ¢ 0 aprendizado das técnicas de programagao, nao de enquadramento de imagens. Figura 4.2: Spritesheet dividida em partes iguais e organizando os estados por linhas © CorelDraw possui a ferramenta Papel Gréfico, que cria um quadric- ulado dividindo uma area em partes iguais. Neste programa e também no Photoshop, Illustrator e em outros, podemos usar as réguas e linhas- guia. Se vocé nao conhece estes recursos, sugiro que pesquise a respeito. Sugestdes: http://coreldrawtips.com/site/using-the-graph-paper-tool-in-coreldraw, http://helpx.adobe.com/br/photoshop/using/grid-guides.html 7 4.2, Carregando imagens fazendo recortes (clipping) Casa do Cédigo 4-2 CARREGANDO IMAGENS E FAZENDO RECORTES (CLIPPING) Carregar uma imagem do servidor pelo JavaScript é bastante simples. Primeiro instanciamos um objeto Image, em seguida, setamos seu atributo sxc para o caminho da imagem: var imgSonic imgSonic.src = 'spritesheet.png'; new Image() 5 A imagem possui o evento onload, que € disparado quando o carrega- mento da imagem esta completo: imgSonic.onload = function() { ... } Quando tivermos muitas imagens, 0 evento onload de cada uma pode incrementar uma porcentagem ou barrinha de “carregando”. Isso ficard para outra hora! Vamos agora fazer o clipping (enquadramento), que é 0 recorte da parte exata da spritesheet que queremos. Por exemplo, suponha que queremos a figurinha na linha 2, coluna 7 (contados a partir de zero). Na hora de pro- gramar as animagées, ficard muito mais facil expressar dessa maneira. No entanto, precisaremos calcular a posigao (x, y) do recorte da imagem. Spritesheet - recorte e enquadramento (clipping) No JavaScript, queremos dizer A classe Spritesheet os mimeros de lin- has e colunas, e escolhemos qual linha queremos animar. As colunas deverio ser alternadas automaticamente. ‘Também passamos no construtor 0 context do Canvas e a imagem a ser desenhada: var context = document. getElementById('canvas_spritesheet_1') .getContext ('2d'); var imgSonic = new Image(); imgSonic.sre = 'spritesheet.png'; // Quero passar: context, imagem, linhas, colunas var sheet = new Spritesheet (context, imgSonic, 3, 8); // Durag&o de cada quadro sheet.intervalo = 60; // “correndo para direita" sheet. linha = 1; // knimagio imgSonic.onload = gameLoop; Na fungao gameLoop, ordeno a Spritesheet que avance um quadro e que desenhe o quadro atual na posi¢ao que eu mandar: function gameLoop() { context.clearRect(0, 0, context.canvas.width, context. canvas height); // Avangar na animagéo sheet. proximoQuadro() ; 7 43. Animagies de sprite — a classe Spritesheet Casa do Cédigo // Onde desenhar 0 quadro atual sheet .desenhar(100, 100); request AnimationFrame(gameLoop) ; ‘Teste pronto, passemos ao esboco da classe Spritesheet function Spritesheet (context, imagem, linhas, colunas) { this.context = context; this. imagem = imagem; this.numLinhas = linhas; this.numColunas = colunas; this.intervalo = 0; this. linha = 0; this.coluna = 0; } Spritesheet. prototype = { proximoQuadro: function() { }, desenhar: function(x, y) { O cleulo do quadro atual é bem simples, bastando incrementar a coluna atual e voltar para zero quando exceder © némero de colunas: proximoQuadro: function() { if (this.coluna < this.numColunas - 1) this. colunat+; else this.coluna = 0; 3, ‘Mas ha um porém: como temos um intervalo de tempo definido para a mudanga de quadro, temos que leva-lo em conta! Isso nao ¢ dificil: vamos manter um registro da ultima mudanga e, a cada ciclo, verificar se ja passou 0 tempo especificado: 78 Casa do Codigo Capitulo 4. Fothas de sprites — spritesheets proximoQuadro: function() { // Momento atual var agora = new Date().getTime() ; // Se ainda nao tem 1timo tempo medido if (! this.ultimoTempo) this.ultimoTempo = agora; // Jé & hora de midar de coluna? if (agora - this.ultimoTempo < this.intervalo) return; if (this.coluna < this.numColunas - 1) this. colunat+; else this. coluna // Guardar hora da ii1tima mudanga this.ultimoTempo = agora; , No método cesenhar, basta fazer o clipping como aprendemos: desenhar: function(x, y) { var larguraQuadro = this. imagem.width / this.numColunas; var alturaQuadro = this.imagem.height / this.numLinhas; this. context. drawImage( this. imagem, larguraQuadro * this.coluna, alturaQuadro * this.linha, largura, altura, zs ye largura, altura Neste ponto, jd temos o Sonic correndo na tela, mas ainda sem alterar sua posicao. 79 4-4. Controle o herdi pelo teclado e veja sua animagio Casa do Cédigo 4-4 CONTROLE O HERGOI PELO TECLADO E VEJA SUA ANIMAGAO Chegou a hora de juntar a Spritesheet com as outras classes criadas an- teriormente (Animacao e Teclado). Vamos controlar 0 Sonic correndo! O sprite do Sonic tera sa fungées de: + Configurar sua respectiva spritesheet; « Lero estado das teclas de seta e mudar o estado do sprite; + Selecionar a linha na spritesheet correspondente ao estado desejados + Responder ao loop de animagao, implementando os velhos métodos atualizare desenhar. arquivo: teste-spritesheet-2.html --> Personagem controlavel e animado Primeiro, facamos as referencias do Canvas, como de costume. Depois, criamos os controles de teclado, de animagao ¢ o Sonic. Este precisara ler 0 80 Casa do Codigo Capitulo 4. Fothas de sprites — spritesheets teclado para movimentar-se. Espero que vocé esteja percebendo que pratica- mente tudo que criaremos seguir este padrao: var canvas = document. getElementById('canvas_sonic'); var context = canvas. getContext('2d') ; new Teclado(document) ; var animacao = new Animacao(context) ; var teclado var sonic = new Sonic(context, teclado); sonic.x = 0; sonic.y = 200; animacao.novoSprite(sonic) ; animacao. ligar(); Criea classe sonic Pelos comandos dados ao Sonic anteriormente, seu esqueleto conterd so- mente 0 context, o teclado, a posicao (x, y) ¢ os métodos de sprite: // arquivo: sonic. js function Sonic(context, teclado) { this. context = context; this.teclado = teclado; this.x = 05 this.y = 05 } Sonic.prototype = { atualizar: function() { }, desenhar: function() { + + Uma das funges da classe Sonic é controlar sua spritesheet, portanto va- mos crid-la no construtor. Optei por sempre receber as imagens pelo constru- tor, pois assim o aplicativo principal tera controle sobre seu evento onload 81 4-4. Controle o herdi pelo teclado e veja sua animagio Casa do Cédigo (por exemplo, para animar uma tela de loading que sera criada posterior- mente). function Sonic(context, teclado, imagem) { this. context = context; this.teclado - teclado; this.x = 0; this.y = 05 // Criando a spritesheet a partir da imagem recebida this.sheet = new Spritesheet (context, imagem, 3, 8); this.sheet.intervalo = 60; Arrumemos o teste para fornecer a imagem: var imgSonic = new Image(); imgSonic.sre = 'spritesheet.png'; var sonic = new Sonic(context, teclado, imgSonic) ; Vamos também ligar a animagio no onload: // Wo final do script imgSonic.onload = function() { animacao. ligar(); Estados do sprite Jé sabemos movimentar um sprite na tela a partir do teclado, No entanto, © sprite do Sonic sé devera ser animado se alguma seta estiver pressionada; do contrario, a animagao de sprites nao deve ocorrer. E ele deve ser animado na direcdo correta. Ai entra a necessidade de termos bem definidos quais os estados possiveis para o sprite Sonic. 82 Casa do Codigo Capitulo 4. Fothas de sprites — spritesheets Na teoria da Orientacao a objetos, um estado é uma forma em que um objeto se encontra e que determina seu comportamento. Em esta- dos diferentes, a mesma mensagem (chamada de método) enviada a um objeto pode produzir comportamentos diferentes. Os estados sao definidos a partir de um ou mais atributos de um objeto. Por exemplo, uma conta bancaria pode estar em um estado NO_VERMELHO caso 0 saldo seja menor que zero. O estado do Sonic dependera de dois atributos: direcao e andando. Estando parado ou andando, ele pode estar virado para a direita ou para a esquerda. Dessa forma, temos quatro estados possiveis para selecionar as im- agens e animar (ou nao) a partir da spritesheet. Vamos definir os as diregdes do Sonic com valores tinicos e iniciar 0 es- tado atual no construtor: var SONIC_DIREITA var SONIC_ESQUERDA = 2 function Sonic(context, teclado, imagem) { this. context = context; this.teclado = teclado; this.x = 0; this.y = 05 // Criando a spritesheet a partir da imagem recebida this.sheet - new Spritesheet(context, imagem, 3, 8); this.sheet.intervalo = 60; // Estado inicial this.andando = false; this.direcao = SONIC_DIREIT: Quero agora testar a seta para direita e movimentar o Sonic de acordo. Caso 0 Sonic ja nao esteja nesse estado, eu tenho que iniciar a respectiva an- imagao na spritesheet. Ao disparar uma mudanga de estado, sempre pode- 83 4-4. Controle o herdi pelo teclado e veja sua animagio Casa do Cédigo mos checar o estado anterior e tomar as medidas necessdrias. No método atualizar, fazemos: if (this. teclado. pressionada(SETA_DIREITA)) { // Se j@ n&o estava neste estado if (! this.andando || this.direcao != SONIC_DIRBITA) { // Seleciono 0 quadro da spritesheet this. sheet.linha = 1; this. sheet. coluna = 0; // continua ... Posicionamos a nossa spritesheet na primeira coluna (zero), da linha rel- ativa ao Sonic indo para a direita. Ainda dentro do primeiro if, devemos entao definir o estado atual, animar a spritesheet e deslocar 0 Sonic: if (this. teclado. pressionada(SETA_DIREITA)) { “ // Configuro o estado atual this.andando = true; this.direcao = SONIC_DIREITA; // Neste estado, a animag&o da spritesheet deve rodar this. sheet. proximoQuadro(); // Desloco o Sonic this.x += this.velocidade; Usamos um novo atributo, velocidade. Vamos inicia-lo com um valor padrio no construtor: // Pode ser ao final do construtor this. velocidade = 10; O mesmo raciocinio sera usado para tratar a movimentagao para a es- querda, Continuando 0 método atualizar: 84 Casa do Codigo Capitulo 4. Fothas de sprites — spritesheets else if (this. teclado.pressionada(SETA_ESQUERDA)) { if (1 this.andando || this.direcao != SONIC_ESQUERDA) { this.sheet.linha = 2; // Atencéo, aqui sera 2! this. sheet.coluna this.andando = true; SONIC_ESQUERDA; this. sheet. proximoQuadro(); this.x -= this.velocidade; // E aqui @ sinal de menos! this.direcao Finalmente, se nenhuma tecla de movimentacio estiver pressionada, 0 Sonic est parado, Devemos, no entanto, checar sua diregao atual para saber qual imagem do Sonic parado devemos usar, se virado para a esquerda ou para a direita, Também nao chamamos 0 método proximoQuadro, pois neste estado nao hé animagao, else { if (this.direcao == SONIC_DIREITA) this.sheet.coluna = 0; else if (this.direcao this.sheet.columa = 1; SONIC_ESQUERDA) this.sheet.linha = 0; this.andando = false; ‘Todo este processamento esti ocorrendo, mas sequer podemos ver 0 re- sultado: 0 método desenhar nao foi implementado! Aqui, podemos sim- plesmente chamaro desenhar da classe Spritesheet: desenhar: function() { this. sheet.desenhar(this.x, this.y); Isso é tudo! Divirta-se controlando o Sonic para os lados. Ele comega ini- cialmente parado e virado para a direita, pois assim definimos no construtor, como estado inicial. 85 4-4. Controle o herdi pelo teclado e veja sua animagio Casa do Cédigo No préximo capitulo, falarei sobre detecgio de colisées, um aspecto fun- damental da grande maioria dos jogos. Seja para golpear um inimigo, ou para coletar argolas, a interacao do sprite do personagem com os outros (inimigos, bloces, plataformas) se dard através da colisdo entre as areas que ocupam na tela. [Lj Personagem controls Figura 4.5: Sprite controlado pelo teclado 86 CaPpiTULO 5, Deteccao de colisdées Chegou a hora de detectarmos colisées, ou seja, saber quando um elemento toca outro na tela, Isso tornara possivel saber quando um inimigo atacou 0 herdi ou foi atingido, ou quando o herdi coletou um item. A partir de colisées, disparamos intimeros acontecimentos que séo a base dos jogos, em especial os de aventura e aco. 5.1 COLISAO ENTRE RETANGULOS Um dos métodos mais simples para detectar colisdes é criar uma caixa delim- itadora (bounding box) ao redor de cada sprite e verificar a intersecgao entre 0s reténgulos: 5:1. Colisio entre retingulos Casa do Cédigo Figura 5.: Sprites colidindo: retangulos (bounding boxes) apresentam inter- secsio Figura 5.2: Sem intersec¢ao, nao ha colisao ‘Mas, quem dera que tudo fosse perfeito! Isso pode nos trazer alguns prob- lemas devido as éreas vazias dos sprites: 88 Casa do Codigo Capitulo 5. Deteegio de colisies Figura 5.3: Retingulos intersectando sem haver a colisio real desejada. O dragio colide com uma rea vazia do sprite do herdi. A abordagem que adotaremos para resolver este problema é a seguinte: cada sprite poders ter varios retingulos de colisio, demarcando suas princi- pais dreas: Figura 5.4: Definindo varios bounding boxes para objetos irregulares Nosso controle agora ficara muito mais preciso e com qualidade bastante satisfatéria para a maioria dos casos. Colisio em pequenas dreas vazias serio imperceptiveis devido a velocidade com que ocorrem as animagées (e sempre poderemos definir mais um reténgulo onde acharmos necessario). Ganhamos também um bénus: na figura 5.4, temos uma agao de ataque (0 herdi tentando socar o dragdo), Podemos diferenciar quem conseguiu atacar quem através de qual retangulo do heréi colidiu com o retangulo do dragio. 89 5.2. Teste da classe Colisor Casa do Cédigo Intersego de retangulos (1, yt) ‘(+ largural, y1) (x2, y2) (<2 + Fargurad, 2) (1, yl-+ aleural) (el + fnrgueal, y1 + altural) (2, y2+ altuna) (2+ Targura?, y2 + altura2) Figura 5.5: Intersec¢io de retangulos Pela geometria, dois retingulos se intersectam se: + (x14 largurat) > x2 E + xi< (x24 larguraa) E + (y+ altura) > y2E + yr< (y2 + altura2) No préximo tépico, iniciaremos o desenvolvimento de uma classe que aplica essas formulas para detectar colisdes entre sprites. 5.2 TESTE DA CLASSE COLISOR Iniciaremos © desenvolvimento da classe responsdvel por colidir objetos. Como de costume, iniciaremos por um pequeno aplicativo de teste onde definimos como queremos interagir com a classe. 90 Casa do Codigo Capitulo 5. Deteegio de colisies Criaremos uma classe para essa nova funcionalidade. Comecaremos criando o aplicativo de teste, onde definimos de que forma queremos interagir coma classe Colisor: arquivo: teste-colisao-1.html --> colisor. js"> ‘bola. js"> No JavaScript, obtemos o canvas e seu context, ¢ criamos algumas bolin- has: var canvas = document. getElementByld('canvas_colisao'); var context = canvas.getContext('2d'); var bi = new Bola(context); bi.x = 200; bi.y = 200; bi. velocidadex bi. velocidadey bi.cor = 'blue'; bi.raio = 20; var b2 = new Bola(context); ou 5.2. Teste da classe Colisor Casa do Cédigo b2.x = 300; b2.y = 300; b2.velocidadex = -5; 2. velocidadeY = 10; b2.cor = 'red'; b2.raio = 30; O préximo passo é criar 0 objeto Colisor e colocar nele os sprites: var colisor = new Colisor(); colisor.novoSprite(b1) ; colisor.novoSprite(b2) ; Agora, facamos o loop de animago. Por enquanto, este loop serd uma simples fungdo. Perceba que, a cada ciclo, apenas realizamos as tarefas que nossos loops ja fazem e, em seguida, mandamos o colisor realizar seu pro- cessamento. O colisor se encarregaré de enviar mensagens aos objetos que colidirem. request Animat ionFrame(animar) ; function animar() { // Limpando a tela contert.clearRect(0, 0, canvas.width, canvas.height) ; // ktualizando os sprites bi.atualizar(); b2.atualizar(); // Desenhando bi.desenhar() ; 2. desenhar(); // Processar colisdes colisor.processar() ; request AnimationFrame(animar) ; 92 Casa do Codigo Capitulo 5. Deteegio de colisies 5.3 A CLASSE COLISOR A classe Colisor, como definimos antes, tera inicialmente 2 métodos: novoSprite e processar. Manteremos um array de sprites, da mesma forma que a classe Animacao: function Colisor() { this.sprites = []; + Colisor.prototype = { novoSprite: function(sprite) { this. sprites. push(sprite) ; }, processar: function() { // hqui faremos a verificagéo de colisdo O coragao da classe 0 método_processar. Aqui, precisamos testar quais sprites esto colidindo entre si. Faremos um algoritmo do tipo “todos contra todos” no array de sprites, usando dois loops for aninhados. Para cada elemento no array (primeiro loop), é testada sua colisdo contra todos os outros (segundo loop). Note que ainda estamos abstraindo a légica de célculo da colisio: processar: function() { for (var i in this. sprites) { for (var j in this. sprites) { // Néo colidir um sprite com ele mesmo if (i == j) contime; // Abstrair a colisao this. testarColisao(this.sprites[i], this.sprites[j]); O novo método testarColisao iri fazer mais um “todos contra to- dos’, agora entre os retangulos do primeiro sprite e os do segundo. Lembre- se de que cada sprite poder definir mais de um retangulo (figura 5.4). Dessa 93 53. A classe Colisor Casa do Cédigo forma, criamos um label coisces para podermos parar os dois loops logo na primeira colisio entre retingulos detectada: testarColisao: function(spritet, sprite2) { // Obter os reténgulos de coliséo de cada sprite var retsi = sprite retangulosColisao(); var rets2 = sprite2.retangulosColisao(); // Testar as colisdes entre eles colisoes: for (var i in retsi) { for (var j in rets2) { // Kinda abstraindo a formula! if (this. retangulosColidem(retsi[i], rets2[j])) { // Eles colidem, vamos notificé-los spritel . colidiuCom(sprite2) ; sprite2. colidiuCom(spritet); // N&o precisa terminar de ver todos os ret&ngulos break colisoes; Esta parte é muito importante, pois definimos uma nova interface que os sprites deverao seguir para poderem participar do colisor. Essa interface ¢ formada por dois métodos: * retangulosColisao: deve devolver um array com os dados de cada retangulo; + colidiuCom(sprite): recebe a notificagao de que o sprite colidiu com outro (recebido como parametro) Nés abstraimos mais um pouco, deixando para o final a fun¢ao que veri- fica se dois retangulos colidem. Considero uma excelente pratica ir isolando as partes “dificeis” ou que nao tém muito a ver com o algoritmo sendo criado: 94 1a do Codigo Capitulo 5. Deteegio de colisies retangulosColidem: function(reti, ret2) { // Férmula de intersegdo de retangulos return (reti.x + reti.largura) > ret2.x &t reti.x < (ret2.x + ret2.largura) &t (ret1.y + reti.altura) > ret2.y && reti.y < (ret2.y + ret2.altura); 5-4 CRIANDO UM SPRITE COLIDIVEL Agora que temos nosso Colisor, vamos criar uma nova versao da classe Bo la seguindo a interface exigida por este, além da interface de animagao ja vista anteriormente: function Bola(context) { this. context = context; this.x = 0; this.y = 05 this. velocidadeX = 0 this. velocidadeY = 0 this.cor = ‘black this.raio = 10; } Bola.prototype = { // Interface de animagéo atualizar: function() { }, desenhar: function() { 3, // Interface de colisdo retangulosColisao: function() { 3, colidiuCom: function(sprite) { + 95 5-4. Criando um sprite colidivel Casa do Cédigo Os métodos atualizar e desenhar néo mudaram em nada. O método atualizar muda a posigao da bola de acordo com suas veloci- dades horizontal e vertical ¢ inverte as velocidades quando ela esta quicando no Canvas. O método desenhar usa a API do contexto “2d” para desen- har a bola em sua posigéo, com a cor e 0 raio definidos. Em caso de divida, consulte a segao 2.4: atualizar: function() { var ctx = this. context; if (this.x < this.raio || this.x > ctx. canvas. width - this.raio) this.velocidadeX *= -1; if (this.y < this.raio || this.y > ctx.canvas.height - this.raio) this.velocidadeY #= -1; this.x += thie. velocidadex; this.y += this. velocidadeY; 3, desenhar: function() { var ctx = this.context; ctx. save(); ctx. fillStyle = this.cor; ctx. beginPathO; ctx.arc(this.x, this.y, this.raio, 0, 2 * Math.PI, false); ctx. fill; ctx. restore(); Para a bola, 0 método retangulosColisao, criaré um tinico retan- gulo que, mesmo assim, deve estar contido em um array. $6 para lembrar, em JavaScript, os colchetes delimitam arrays e as chaves delimitam objetos. Temos, portanto, um objeto dentro de um array: retangulosColisao: function() { return [ { 96 Casa do Codigo Capitulo 5. Deteegio de colisies x: this.x - this.raio, // this.x é 0 centro da bola yi this.y - this.raio, // this.y idem largura: this.raio * 2, altura: this.raio * 2 Como a posisio (xy) da bola define seu centro, descontamos 0 raio para achar a posigdo do seu retingulo circundante, que na realidade é um quadrado. © lado desse quadrado é igual ao dobro do raio da bola: |e a | Figura 5. Retangulo circundante da bola Falta agora 0 método colidiuCom. Por enquanto, vamos mostrar uma mensagem de alerta: colidinc function(sprite) { alert('PA!'); Faga o teste e veja se a mensagem aparece: 97 55. Melhorando 0 codigo Casa do Cédigo [[)Deteecso de colisses x 7 flewinoreredersonD acumertost 2920-7 Figura 5.7: Colisao detectada No entanto, a mensagem aparece muitas vezes pelos seguintes motivos: + Estamos detectando colisées repetidas. ‘Testamos A e vimos que colidiu com B. Ao testar B, nao precisamos colidi-lo com A novamente! + Sao duas bolinhas dando a mesma resposta a colisao, entao tudo isso ocorre em dobro! + Resultado: quatro “PA!” por colisao. + Fora o fato de as bolinhas continuarem seu trajeto normalmente, fazendo com que passem uma “dentro” da outra e provoquem novas colisées. 5.5 MELHORANDO 0 CODIGO Resolvendo a deteccao repetida Do jeito que estamos programando, cada colisao ¢ detectada duas vezes. Fize- mos a seguinte sequéncia de agdes: + Pegamos o primeiro sprite (vamos chamé-lo de A); 98 Casa do Codigo Capitulo 5. Deteegio de colisies + Fazemos um loop em todos os outros e testamos contra A; + Detectamos uma colisio com outro sprite (que chamaremos de B + Enviamos uma mensagem para A e para B ‘Mas o processo nao para ai, chegou a vez de testar B! « Testamos B contra os outros; + Detectamos sta colisio com A (que jé foi detectada anteriormente); + Enviamos as mensagens novamente para A e para B. Para resolver o primeiro problema, vamos manter um registro de colises jd testadas. Isto pode ser feito de diversas formas, mas queimando neurénios eu achei mais facil usar um objeto associativo contendo arrays, onde eu asso- cio um sprite aos outros com quem ele jé colidiu. Veja: // Exemplo teérico // Crio um objeto vazio var jaTestados = new Object (); // Associo um sprite a um array de outros // Estou dizendo que a nave j4 colidiu com o meteoro e o item var jaTestados[nave] = [meteoro, iten]; Infelizmente, os elementos de jaTest ados nao podem ser outros ob- jetos. Nesse codigo, nave deve ser uma string, pois as propriedades de um objeto podem ser expressas por strings usando a sintaxe de [colchetes] (veja a segdo 1.4). Preste bastante atengao ao exemplo tedrico a seguir. Se gerarmos uma string tinica para cada sprite, podemos associar cada uma a um array den- tro desse objeto associativo. ‘Tendo as strings devidamente armazenadas no objeto, podemos verificar se a colisao ja foi testada: 99 55. Melhorando 0 codigo Casa do Cédigo // Exemplo teérico // & cada ciclo do loop estamos testando 2 sprites // Criar as identificagdes var idl = stringUnica(spritet) ; var id2 = stringUnica(sprite2) ; // Criay os arrays se n&o existirem if (! jaTestados[id1]) jaTestados[idi] = (1; if (! jaTestados[id2]) jaTestados[id2] = [1; // Testar a colis&o se jé nfo foi testado idi com id2 testarSeNaoRepetido(idt, id2); // Registrando o teste para evitar repetigao jaTestados [idi] .push(id2); jaTestados [id2] .push(idt); Para verificar se o teste atual nao é repetido, podemos usar 0 método indexof dos arrays. Por exemplo, jaTestados [idl] .indexOf(id2) devolve a posi 0 da string id2 dentro do array associado 4 idl no objeto, ou 0 valor -1 caso ainda nao exista: // Exemplo teérico (parte final) // Nerificar se idi com id2 j4 foi testado if (! (jaTestados[id1] .indexOf(id2) >= 0 || jaTestados[id2].indexOf(id1) >= 0) ) { // Aqui testamos a coliséo Vamos, finalmente, para a parte pritica. Primeiro, crie 0 método st ringUnica, que gerard uma string para o sprite a partir dos dados de seus retangullos de colisao: stringUnica: function(sprite) { var str = ''5 var retangulos sprite. retangulosColisao() ; 100 1a do Codigo Capitulo 5. Deteegio de colisies for (var i in retangulos) { str ++ 'x:! + retangulos[i].x + ',' + + retangulos[i].y + ',' + + retangulos[i].largura + ',' + + retangulos[i].altura + '\n'; return str; Acompanhe a nova implementagao do método processar, acrescen- tando os novos testes. Iniciamos com um objeto vazio e, a cada verificagao, geramos uma string para cada sprite, verificamos se seus arrays jA existem e se jd foi testada essa colisio. Caso nao, nés a testamos e a registramos: processar: function() { // Tnicio com um objeto vazio var jaTestados = new Object(); for (var i in this.sprites) { for (var j in this.sprites) { // Néo colidir um sprite com ele mesmo if (i == j) contimue; // Gerar strings tnicas para os objetos var idi = this.stringUnica(this.sprites[i]); var id2 = this.stringUnica(this.sprites[j]); // Criar 0s arrays se n&o existirem if (! jaTestados[idt]) jaTestados[iai] = (1; if (! jaTestados[id2]) jaTestados[id2] = []; // Teste de repetigaéo if (! (jaTestados[id1] . indexOf (id2) jaTestados [id2] .indexOf(idt) >= 0) ) { // Mostrair a coliséo this.testarColisao(this. sprites [il], this.sprites[j]); 101 55. Melhorando 0 codigo Casa do Cédigo // Registrando o teste jaTestados [id1] . push(id2) ; jaTestados [id2] .push(idt) ; 3, Se vocé rodar o programa agora, vera que reduzimos para dois “PA!” por colisao, pois sao duas bolinhas respondendo. Um tratador geral de colisdes Por que somente as bolinhas (sprites) podem responder & colisio? Ha casos, por exemplo, em que eu quero uma resposta tinica € nao € necessario que os dois objetos respondam. Que tal poder fazer: // Exemplo teérico colisor.aoColidir = function(sprite1, sprite2) { // Resposta tnica Oatributo acColidir seria uma fungao de callback executada em toda e qualquer colisio. Esta funcao receberia como pardmetros os sprites que colidiram. No construtor da classe Colisor, inicie 0 atributo acColidir com null, somente para documenté-lo: function Colisor() { this.sprites = []; this.aoColidir = null; No método testarColisao, no ponto em que as mensagens sao envi- adas, acrescente a chamada a esse callback. Usamos um i para verificar se foi atribuida uma fungao: Mt 102 Casa do Codigo Capitulo 5. Deteegio de colisies if (this.retangulosColidem(retsi[i], rets2[j])) // Eles colidem, vamos notificé-los sprite. colidiuCom(sprite2) ; sprite2. colidiuCom(spritet); // Tratador geral if (this.aoColidir) this.aoColidir(spritel, sprite2); // No precisa terminar de ver todos os retangulos break colisoes; Mt No aplicativo de teste, logo apés a criagio do colisor, podemos atribuir a seguinte funcao: var colisor = new Colisor(); colisor.novoSprite(b1) ; colisor.novoSprite(b2) ; colisor.aoColidir = function(si, 52) { alert ('PA'); Eo método colidiuCom da Bola ficaré com o corpo vazio (por en- quanto): colidiuCom: function(sprite) { Neste ponto, temos apenas um “PA!” para cada deslocamento. Lembre-se de que as bolinhas continuam sua trajetéria em linha reta e provocam novas colis6ées enquanto passam uma pela outra. Fazendo os sprites quicarem Se fizermos as bolinhas quicarem uma na outra, resolvemos completa- mente o problema do excesso de colisdes. O segredo, aqui, é verificar qual 103 55. Melhorando o cédigo Casa do Cédigo bolinha esté em que lado, e fazer com que passe a ir nesse sentido (nao im- porta em que sentido esteja indo). A figura 5.8 representa isso na horizontal: vai para esquerda 4 vai para direita Figura 5.8: O sprite que est & esquerda vai para a esquerda, e 0 que esta A direita vai para a direita © mesmo vale para a vertical: vai para cima t 4 vai para baixo- sprite que esta acima vai para cima, ¢ 0 que esta abaixo vai para 104 Casa do Codigo Capitulo 5. Deteegio de colisies Faremos isto no método colidiuCom da classe Bola. Para forcar a bola a ir para um determinado lado, obtemos os médulos das velocidades com Math. abs ecolocamos um sinal negativo nos casos em que a velocidade passa a ser negativa (para esquerda e para cima): colidiuCom: function(sprite) { if (this.x < sprite.x) // Estou na esquerda this.velocidadeX = -Math.abs(this.velocidadeX); // - else this. velocidadeX = Math.abs(this.velocidadex); // + if (this.y < sprite.y) // Estou acima this.velocidadeY - -Math.abs(this.velocidadeY); // - else this. velocidadeY = Math.abs(this.velocidadeY); — // + ‘Temos agora uma tinica mensagem “PA!” por colisio. Se vocé quiser ver apenas as bolinhas quicarem, basta comentar 0 alert ou simplesmente re- mover o tratador geral: // Remova este cédigo ou comente o corpo da fungao: colisor.aoColidir = function(st, s2) { /falert('PA'); Aqui, as possibilidades so infinitas. No decorrer do desenvolvimento de nosso jogo, faremos os objetos atingidos por tiros sumirem, criaremos novos sprites (explodes), aumentamos indicadores na tela (coleta de item) etc. No préximo capitulo, faremos as primeiras experiéncias para 0 jogo de nave ilustrado no primeiro capitulo. 105 CapiruLo 6 Iniciando o desenvolvimento do jogo ‘A partir deste ponto daremos infcio ao desenvolvimento de nosso jogo de nave. O game engine ainda tera muitos acréscimos e melhorias, conforme formos precisando de novos recursos. Em algum momento, ele ficaré maduro e pronto para uso, mas minha ideia é mostrar como ele pode sempre continuar evoluindo conforme vamos desenvolvendo jogos com ele. 6.1 ANIMAGAO DE FUNDO COM EFEITO PARALLAX ‘A primeira coisa que faremos é 0 fundo. Se vocé ainda nao fez 0 download dos arquivos deste livro, faga em http://github.com/EdyKnopfler/games-js/ archive/master.zip, pois usaremos as imagens contidas ali. 6, Animagio de fundo com efeito parallax Casa do Cédigo O cenério do jogo terd o seguinte aspecto, em resolugao de 500 por 500 pixels: Figura 6.1: Cenario do jogo de nave O fundo, no entanto, sera composto por 3 imagens separadas: uma para © gradiente, outra para as estrelas e outra para as nuvens. Estas duas ulti- mas terao fundo transparente, para podermos encaixar umas sobre as outras, como camadas: Somente Acrescentando astrelas com Acrescantando nuvens com oradiente fundo transparente fundo transparente, Figura 6.2: Combinando os elementos do fundo 108 1a do Codigo Capitulo 6. Iniciando 0 desenvolvimento do jogo Cada imagem possui 500 pixels de largura por 1000 pixels de altura (por- tanto, o dobro da area do jogo). Elas rolario pelo cenario a velocidades difer- entes, criando um efeito denominado parallax (paralaxe): Figura 6.3: Os elementos do fundo rolam a velocidades diferentes 109 6, Animagio de fundo com efeito parallax Casa do Cédigo PARALAXE ‘Um avido no céu parece voar a uma velocidade incrivelmente lenta. No entanto, se estivéssemos ld em cima e ele passasse por nés, o veriamos a toda velocidade. ‘Da mesma forma, viajando por uma estrada, o mato diante do meio- fio e as placas passam velozmente, enquanto a serra ao longe se afasta devagar, Esse efeito de velocidade aparente, na Fisica, ¢ denominado par- alaxe. Quanto mais distante esta um objeto, menor sera sua veloci- dade percebida por nés. Olivro Core HTMLs Canvas, de David Geary, traz um exemplo muito bem feito, que pode ser conferido aqi http://corehtmlscanvas.com/code-live/chos/example-s.17/example. html Obs.: entre desenvolvedores de jogos, 0 mais comum ¢ nos referir- mos ao efeito pelo nome em inglés (parallax), razo pela qual usarei esta forma daqui para frente. Durante a rolagem, cada imagem deverd ser desenhada no minimo duas vezes, de forma a cobrir toda a area de desenho durante a rolagem (figura 6.4). Se fosse uma imagem menor, terfamos que desenhé-la mais vezes. A cada ciclo, nés emendamos os desenhos a uma altura diferente (mareada com uma linha vermelha): no Casa do Codigo Capitulo 6. Iniciando 0 desenvolvimento do jogo Figura 6.4: Cada imagem de fundo é desenhada quantas vezes forem necessarias para cobrir a area do jogo. Com imagens maiores que o Canvas, duas vezes sio suficientes Repare que os pontos inicial e final do gradiente tem de possuir a mesma cor para serem emendados (lembre-se disso ao criar as imagens em seu pro- grama grafico predileto). Da mesma forma, os extremos das outras figuras precisam encaixar-se perfeitamente! Por exemplo, para criar as nuvens, pode- mos proceder da maneira descrita nas figuras 6.5 € 6.6: am 6, Animagio de fundo com efeito parallax Casa do Cédigo Figura 6.5: Quebramos uma nuvem pelo centro te Figura 6.6: Viramos cada metade de cabega para baixo e encaixamos nas bor- das da imagem Tomando esses cuidados ao produzir as imagens de fundo de seus games, n2 Casa do Codigo Capitulo 6. Iniciando 0 desenvolvimento do jogo a programacio ficara muito facil. Vamos criar a classe Fundo, que serd basi- camente um sprite como todos os outros. Teste para a classe Fundo Para a criagao deste teste, crie uma pasta de nome img na mesma pasta onde o HTML sera salvo, e copie nela os arquivos fundo-espaco.png, fundo-estrelas.pnge fundo-nuvens .png do pacote que vocé baixou. Vocé pode criar ou procurar outras imagens com fundo transparente tam- bém, mas certifique-se de recorté-las ou dimensioné-las para 500x100. ee 2 gy [Gi shomejederson/Dacumentos/Livro Jogos DISPOSITIVOS " Aste | i © Volume 16 68 ima taste- fundo-L.htm| LUGARES + G ederson TeBicelie ereere [Ares de trabalho | Nest paste Figura 6.7: Crie uma pasta junto ao HTML e copie as imagens nela O HTML para o teste nao tem nada de especial: Fundos rolando em Parallax 13 6, Animagio de fundo com efeito parallax Casa do Cédigo OBSERVAGAO Ao incluir arquivos ja criados anteriormente (no caso, animacao. js), pegue sempre o do capitulo mais recente. Neste caso, a versio mais recente da classe Animacao é do capitulo 4. A cada capitulo ou tépico, o game engine podera softer alteragdes. No JavaScript, inicialmente mandamos carregar as imagens: var imgEspaco - new Tmage(); imgEspaco.src = 'img/fundo-espaco.png' ; var imgEstrelas = new Image(); imgEstrelas.src = 'img/fundo-estrelas.png'; var imgluvens = new Image(); imgWuvens.src = ' img/fundo-nuvens. png! ; Em seguida, temos que esperar todas serem carregadas. Como demos, isto é feito através do evento onload. Mantemos um contador de quantas j4 esto carregadas e, quando todas estiverem prontas, a aplicagao apren- pode de fato “iniciar’ var carregadas = 0; var total = 3; imgEspaco. onload = carregando; imgEstrelas.onload = carregando; nq Casa do Codigo Capitulo 6. Iniciando 0 desenvolvimento do jogo imgiiuvens.onload = carregando; function carregando() { carregadas++; if (carregadas total) iniciar(); Na funcio iniciar, fazemos as operagées habituais: inicializamos o Canvas, criamos os sprites (que aqui serao 0s fundos rolando) e os incluimos no objeto animador: function iniciar() { var canvas = document. getElementByTd(' canvas_fundo') ; var context = canvas.getContext('2d'); // Passo 0 context e a imagem para os objetos Fundo var fundoEspaco = new Fundo(context, imgEspaco) ; var fundoEstrelas = new Fundo(context, imgEstrelas); var fundoNuvens = new Fundo(context, imgiuvens) ; // Cada um a uma velocidade diferente fundoEspaco. velocidade = 3; fundoEstrelas.velocidade = 7; fundoNuvens.velocidade = 10; var animacao = new Animacao(context); animacao. novoSprite(fundoEspaco) ; animacao. novoSprite(fundoEstrelas) ; animacao. novoSprite(fundoluvens) ; animacao. ligar(); Iniciando a classe Fundo A préxima etapa é iniciar a classe Fundo. Sempre adotamos a pratica de receber as imagens pelo construtor, para permitir que a aplicagio controle quando todas estao carregadas: Pirceqeve: eanaauys function Fundo(context, imagem) { 45 6, Animagio de fundo com efeito parallax Casa do Cédigo this.context = context; this. imagem = imagem; this. velocidade = } Fundo. prototype = { function() { desenhar: function() { Lembre-se de que cada fundo deve ser desenhado duas vezes, emenda- dos pelas extremidades em posicées diferentes (figura 6.4). Crie o atributo posicaoEmenda e 0 inicialize no construtor com o valor zero: function Fundo(context, imagem) { this. context = context; this. imagem = imagem; this. velocidade this. posicaoEmenda No método desenhay, desenhamos as duas copias da imagem emen- dadas nessa posicao: desenhar: function() { var img = this.imagem; // Para facilitar a escrita :D // Primeira cépia var posicaoY = this.posicaoEmenda - img. height; this. context.drawImage(img, 0, posicaoY, img.width, img. height) 5 // Segunda cépia posicaoY - this.posicaoEmenda: this. context.drawImage(img, 0, posicaoY, img.width, img. height) 5 6 Casa do Codigo Capitulo 6. Iniciando 0 desenvolvimento do jogo No método atualizar, incrementamos a posicdo da emenda ea volta- mos para zero caso a imagem de cima desga mais que o inicio do Canvas. A figura 6.8 adiante ilustra o que acontece: atualizar: function) { // Atuallizar a posig&o de emenda this. posicaoEmenda += this. velocidade; // Emenda passon da posigéo if (this.posicaoEmenda > this. imagem. height) this. posicaoEmenda = 0; 3, Neste ponto, temos que voltar a posicao de emends para zero. Inicio da rolagem Figura 6.8: Imagens desceram muito, hora de reposiciond-las u7 6.2. Controle da nave na horizontal ena vertical Casa do Cédigo Jé estamos viajando pelo espaco sideral! (F)Fundos rolando em Pa ef LE fleshomejedersonyt Q. Figura 6.9: Imagens de fundo rolando em parallax 6.2 CONTROLE DA NAVE NA HORIZONTAL E NA VERTI- CAL ‘Vamos criar uma nave controlavel, inicialmente em um novo teste em branco, e depois juntando com o fundo em parallax. No pacote de download do livro, existe o arquivo nave .png (figura 6.10), com dimensées de 36 por 48 pixels. Vocé pode procurar ou criar outra imagem, mas certifique-se de que tenha fundo transparente. us. Casa do Codigo Capitulo 6. Iniciando 0 desenvolvimento do jogo Figura 6.10: Nave espacial controlavel Crie uma nova pagina para testarmos o controle da nave. Esta pagina usard as classes Animacao, Teclado e Nave (a ser criada): arquivo: teste-nave. htm] Nave Espacial Controlavel // A nave andaré por toda a tela! No JavaScript, temos o que ja fizemos muitas vezes: + referenciar o canvas e 0 context; « instanciar as classes do game engine; + instanciar o sprite (no caso, a nave) e sua imagem; + aguardar a imagem carregar; ug 6.2. Controle da nave na horizontal ena vertical Casa do Cédigo + iniciar a animacao com a nave como sprite. // Canvas e contexto var canvas = document. getElementById('canvas_nave'); var context = canvas.getContext ('2d'); // Teclado e animag&o (game engine) var teclado = new Teclado(document) ; var animacao = new Animacao(context) ; // Sprite da nave e sua imagem var imgllave = new Image(); imgNave. src = 'img/nave.png var nave = new Nave(context, teclado, imgNave); animacao.novoSprite(nave) ; // Quando carregar a imagem, iniciar a animago imgNave.onload = function() { animacao. ligar(); A classe Nave receberd no construtor o context, o controlador do teclado ea imagem para desenho: it equest ames function Nave(context, teclado, imagem) { this. context = context; this.teclado = teclado; this. imagem - imagem; } Nave. prototype = { function() { atualiza }, desenhar: function() { No método atualizar, lemos o estado de cada seta do teclado e move- 120 Casa do Codigo Capitulo 6. Iniciando 0 desenvolvimento do jogo mos a nave de acordo, Para as setas & direita e abaixo, tivemos que considerar o tamanho do canvas e descontar o tamanho da nave, para que nao ultrapas- sasse a borda: atualizar: function() { if (this. teclado.pressionada(SETA ESQUERDA) && this.x > 0) this.x -= this. velocidad if (this.teclado.pressionada(SETA_DIREITA) && this.x < this.context.canvas.width - this. imagem. width) this.x += this.velocidade; if (this. teclado.pressionada(SETA_ACINA) && this.y > 0) this.y -= this.velocidade; if (this. teclado.pressionada(SETA_ABAIXO) && this.y < this.context.canvas.height - this. imagem.height) this.y this.velocidade; Onservagio Aqui usamos quatro if, sem ouso do 1se, para permitir que mais de uma seta possam estar pressionadas ao mesmo tempo. Isto nos per- mitiré mover a nave na diagonal! A nossa necessidade criou trés atributos: x, y € velocidade. Vamos inicid-los no construtor: function Nave(context, teclado, imagem) { this. context - context; this.teclado = teclado; this. imagem = imagem; this.x = 05 this.y = 05 this. velocidade = 0. 12a 6.2. Controle da nave na horizontal ena vertical Casa do Cédigo E setar valores adequados no cédigo de teste, no evento onload da im- agem, pois calcularemos a posicao a partir das medidas da imagem e, por- tanto, ela deve estar carregada: // Quando carregar a imagem, iniciar a animacdo imgNave.onload = function() { // Centralizada na horizontal, // alinhada embaixo na vertical nave.x = canvas.width / 2 - imgNave.width / 2; nave.y = canvas.height - imgNave.height; nave. velocidade = 5; animacao. ligar(); No método desenhar, copiaremos a imagem da nave sem redimensioné-l desenhar: function() { this. context.drawImage(this.imagem, this.x, this.y, this. imagem.width, this.imagem.height); Por tiltimo, certifique-se de que, no arquivo teciado. js, temos os cédi- gos de todas as teclas de que precisamos // Cédigos de teclas - aqui véo todos os que forem necessérios var SETA_ESQUERDA = 37; var SETA_ACIMA = 38; var SETA_DIREITA - 39; var SETA_ABAIKO = 40; var ESPACO = 32; Voild! Nossa nave move-se em todas as direcdes, sem ultrapassar os lim- ites do Canvas! 122, Casa do Codigo Capitulo 6. Iniciando 0 desenvolvimento do jogo (J) Nave Especial Control: x (GD fileyhomeyederson/Decumentes Figura 6.11: Controlando a nave pelas setas do teclado 6.3 EFETUANDO DISPAROS Agora a coisa vai comegar a ficar divertida: nossa nave ja se mexe, é hora de fazé-la disparar tiro: 123 63. Bletuando disparos Casa do Cédigo | Espacial Que Atir a | G | D Aleyhomejedersorydex ys Figura 6.12: Pazendo a nave atirar Faga uma cépia do arquivo do teste anterior ( teste-nave.html) e renomeie-a para teste-tiros.html. Logo antes do evento onload da imagem, coloque 0 cédigo que manda a nave atirar ao disparar a tecla Espago: // Wunca foi tao facil mandar uma nave atirar! teclado.disparou(ESPACO, function() { nave.atirar(); Hs // Quando carregar a imagem, iniciar a animagdo imgNave.onload = function() { Agora vamos criar 0 método atirar na classe Nave. Criar um tiro é facil, 0 problema é inclu{-lo na animacao de sprites. Nao temos nenhuma referéncia ao objeto animacao! 124 Casa do Codigo Capitulo 6. Iniciando 0 desenvolvimento do jogo 3, // Wao esquega da virgula no tiltimo método quando criar outro atirar: function() { var t = new Tiro(this.context) ; // Como incluir o novo tiro na animagSo? Se vocé fez.a ligdo de casa direitinho, poderia pensar: recebo pelo constru- tor! Sim, é uma grande ideia, e eu apoio. Receber as coisas pelo construtor é bem melhor do que fazer referéncia a variavel an imacao do aplicativo dire- tamente. E se eu tento reaproveitar esta classe onde nao tenho esta varidvel? Receber pelo construtor forcaria o aplicativo a fornecer uma animagao para anave. No entanto, pensando um pouco mais além, pode ser que eu tenha dezenas (ou centenas) de variedades de sprites que precisam criar outros na animagio. Por que nio damos. todos eles uma referénciad animacao? Basta abrir a classe Animacae e modificar seu método novoSprite: novoSprite: function(sprite) { this. sprites.push(sprite); sprite.amimacao = this; i Agora qualquer sprite incluido tem uma referencia ao objeto que controla aanimagio! Nao é legal? Podemos voltar ao método atirar da Nave e usar o objeto que ele recebeu: atirar: function() { var t = new Tiro(this. context) ; this. animacao.novoSprite(t) ; Enfim, vamos criar o esqueleto da classe Tiro: /f arquivo: tiro.je function Tiro(context) { 125 63. Bletuando disparos Casa do Cédigo this. context = context; + Tiro.prototype = { atualizar: function() { }, desenhar: function() { Onde o tiro vai aparecer? Na ponta da nave, vocé vai pensar. Dessa forma, 6 interessante recebermos a nave pelo construtor: function Tiro(context, nave) { this. context = context; this.nave = nave; No método at iray, a nave passa a si mesma para 0 tiro se posicionar: atirar: function®) { var t = new Tiro(this.context, this); this. animacao.novoSprite(t) ; ‘Tendo uma referéncia nave, podemos posicionar o tiro. Faremos isso no proprio construtor: function Tiro(context, nave) { this.context = context; this.nave = nave; // Posicionar o tiro no bico da nave this.largura = 4; this.altura = 20; this.x = nave.x + nave.imagen.width / 2 - this.largura / this.y = nave.y - this.altura; this. velocidade = 105 126 Casa do Codigo Capitulo 6. Iniciando 0 desenvolvimento do jogo Para atualizar o tiro na animagao, apenas o faremos subir na tela, sub- traindo sua posicdo y: atualizar: function() { this.y -= this. velocidade; }, Para desenhé-lo, faremos um pequeno retingulo. Criamos um novo atributo, cor, para que possa ser alterado a gosto: desenhar: function() { var ctx = this.context; ctx. save(); ctx.fillStyle = this.cor; ctx.fillRect(this.x, this.y, this.largura, this.altura); ctx.restore(); No construtor, vamos inicializar cor com uma cor mais escura, pois a nave ainda esta em fundo branco: // Escolha sua cor! this.cor = 'red'; Por tiltimo, nao se esquega de colocar a referéncia ao arquivo tire. js na pagina HTML: Nave Espacial Que Atira Divirta-se! No préximo capitulo, iremos criar inimigos para 0 nosso herdi. 127 63. Bletuando disparos Casa do Cédigo ExERCicios: Se vocé entendeu bem os conceitos, as tarefas a seguir ndo deverio ser tio dificeis assim: + Tente integrar o fundo em parallax com a nave se mexendo e ati- rando. + Tente também atirar para os lados e para a diagonal. Isto requerera ler o estado das teclas de diregao e dar velocidade horizontal a0 tiro. Comece a brincar e a experimentar! 128 Capiruto 7 Criando inimigos Chegou a hora de adicionarmos um pouco de emogio verdadeira em nosso game. De nada adianta termos um jogo e nao termos quem combater, nio é mesmo? Portanto, criaremos agora os primeiros inimigos. Neste capitulo, utilizaremos bastante a classe Colisor para detectar quando os inimigos colidirem com os tiros e com a nave. No pacote de arquivos do livro, esta presente a imagem ovni. png. Nosso objetivo sera fazer OVNIs cairem do alto da tela, em velocidades e posigdes x diferentes. 72. Primeiro teste com nave e inimigos Casa do Cédigo SS Figura 7.1: OVNIs cairao na tela! 7-1 PRIMEIRO TESTE COM NAVE E INIMIGOS Como de costume, vamos comecar com a pagina contendo 0 aplicativo de teste. Veja como a quantidade de tags // Tnimigos terriveis tentarao destrui-1 Para comegar o JavaScript, vamos carregar as imagens, pois os sprites pre- cisarao recebé-las pelos construtores. Como sao duas, usaremos uma fungao carregando para detectar quando elas estiverem prontas: // Imagens var imgiiave = new Image(); imgNave.sre = ‘img/nave.png imgNave. onload = carregando; var imgOvni = new Image(); imgOvni.arc = 'img/ovni.png'; imgOvni.onload = carregando; Em seguida, a criagao dos objetos basicos: canvas e contexte, teclado, an- imagio, sprite da nave e colisor. Também configuramos o disparo pela tecla Espaco. Pode parecer repetitive, masa ideia ¢ essa mesmo: tornar o negdcio padronizado (e dessa forma vocé fixa melhor os conceitos)! 431 72. Primeiro teste com nave e inimigos Casa do Cédigo // Inicializag&o dos objetos var canvas - document. getElementByTd('canvas_inimigos'); var context = canvas.getContext('2d'); var teclado = new Teclado(document) ; var animacao = new Animacao(context) ; var nave = new Nave(context, teclado, imgNave); animacao. novoSprite(nave) ; var colisor = new Colisor(); colisor.novoSprite (nave) ; teclado.disparou(ESPACO, function() { nave.atirar(); Ys Agora crie as fungées carregando, que monitora o carregamento das imagens, e iniciar, chamada por carregando quando todas as imagens estiverem prontas, Na fungdo iniciar, posicionamos a nave, iniciamos a animagio e definimos que a cada 1000 milissegundos um novo inimigo sera criado na tela, usando a fungdo set Interval do JavaScript: // Carregamento e inicializagéo var carregadas = 0; function carregando() { carregadas++; if (carregadas 2) iniciar(); function iniciar() { nave.x = canvas.width / 2 - imgNave.width / 2; nave.y = canvas.height - imgNave.height; nave. velocidade animacao. Ligar(); setInterval(novo0vni, 1000); 132 Casa do Codigo Capitulo 7. Criando inimigos Quem criaré o inimigo é a fungio novoovni. Sabendo que a imagem possui 64x32 pixels, nds a posicionamos acima da area visivel do jogo, de- scontando 32 pixels de altura, Para dar um pouco de emogao ao game, a posicao horizontal e a velocidade sao definidas aleatoriamente para cada novo inimigo, com 0 auxilio de Math. randome Math. floor. Também é preciso colocar 0 inimigo no colisor: // Criagéo dos inimigos function novoOvni() { var omni = new Ovni(context, imgOvni) ; // Minimo: 5; méximo: 20 ovni.velocidade = Math.floor( 5 + Math.random() * (20 - 5 + 1) ); // Minimo: 05 // wéximo: largura do canvas - largura do ovni ovni.x = Math. floor(Math.random() * (canvas.width - imgOvni.width + 1) ); // Descontar a altura ovni.y = -imgOvni-height; animacao. novoSprite(ovni) ; colisor.novoSprite (omni) ; 133, 72. A dase Ovni Casa do Cédigo COMBINANDO MATH.RANDOM E MATH.FLOOR O método random retorna um niimero aleatério fraciondrio, entre o e1, Para gerar um numero aleatério inteiro, usamos a formula: function aleatorio(min, max) { return min + Math. floor (Nath.random() * (max - min + 1));| © método floor descarta a parte fraciondria do resultado, arredondando-o para o inteiro de menor valor (ndo © mais préximo). PADRONIZANDO A CODIFICAGAO Vamos sempre adotar a seguinte sequéncia de codificacéo ao criar novos aplicativos: + Primeiro, carregamos as imagens, pois os objetos do jogo depen- dem delas; + Em seguida, instanciamos os objetos do game engine (animag¢ao, teclado, colisor) ¢ 0s sprites, usando as imagens quando necessarios + Por tiltimo, criamos as fungdes de inicializacao, que s6 devem ex- ecutar quando as imagens estiverem completamente carregadas. 7.2 ACLASSE OVNI Vamos criara classe Ovni, inicialmente sem se preocupar com a interface de colisio. Como de costume, comecamos construindo um esqueleto a partir do que idealizamos para a classe na pagina HTML: 134 Casa do Codigo Capitulo 7. Criando inimigos // arquivo: ovni.js function Ovni (context, imagem) { this. context = context; this. imagem = imagem; this.x = 05 this.y = 05 this. velocidade = + Ovni.prototype = { atualizar: function() { 3, desenhar: function() { Se o objetivo é fazer o inimigo descer pela tela, 0 método atualizar tem de incrementar a posicao y: atualizar: function() { this.y += this. velocidade; , Jao método desenhar apenas desenha a imagem que foi recebida no construtor: desenhar: function() { var ctx = this.context; var img = this. imagem; ctx.drawImage(img, this.x, this.y, img.width, img-height); Ja temos uma nave que atira (programada no capitulo anterior) e OVNIs descendo no mesmo aplicativo (figura 7.2)! 73 ADICIONANDO FUNDO EM PARALLAX No capitulo anterior, criamos um fundo consistindo de varias imagens em movimento, cada uma em velocidade diferente, criando um efeito que 135 73. Adicionando fundo em parallax Casa do Cédigo chamamos parallax, Chegou a hora de integrar esse fundo ao nosso game. Primeiro, faga uma cépia do arquivo teste-inimiqos .htm1, dando- Ihe o nome teste-inimigos-e-fundo. html. Iremos fazer algumas al- teragGes em determinados pontos. No inicio do cédigo, onde carregamos as imagens, adicione o carrega- mento das trés imagens de fundo: // Imagens var imgEspaco = new Image(); imgEspaco.src = 'img/fundo-espaco.png'; imgEspaco. onload = carregando; var imgEstrelas - new Image(); imgEstrelas.src = 'img/fundo-estrelas.png'; imgEstrelas.onload = carregando; var imgiuvens = new Image(); imgNuvens. src = 'img/fundo-nuvens png’ imgNuvens.onload = carregando; // Imagens da nave e dos ovnis... Agora temos cinco imagens no total, portanto temos que alterar a fun¢io carregando para contar até 5. Uma varidvel total nos permitira alterar este parametro mais facilmente: // Carregamento e inicializagéo var carregadas = 0; var total = 5; function carregando() { carregadas++; if (carregadas total) iniciar(); Precisamos adicionar os fundos a animagio antes dos outros sprites, de forma que eles sejam desenhados primeiro. Na seco onde inicializamos os objetos do game engine e os sprites, antes da criagdo da nave, adicione o cédigo: 136 Casa do Codigo Capitulo 7. Criando inimigos // Primeiro os objetos do game engine var canvas = var context var teclado =... var animacao = ... // Em seguida as imagens de fundo var fundol - new Fundo(context, imgEspaco); fundol.velocidade = 3; animacao.novoSprite(fundo1) ; var fundo2 = new Fundo(context, imgEstrelas); fundo2. velocidade = 7; animacao. novoSprite(fundo2) ; var fundo3 = new Fundo(context, imgNuvens); fundo3.velocidade = 10; animacao. novoSprite(fundo3) ; // Depois do fundo, a nave e outros sprites var nave =... 137 73. Adicionando fundo em parallax Casa do Cédigo ISR nii gerbes 1 fle uhomeedersonDorumert oiie)r0)90520477 Figura 7.3: Cena com OVNIs, nave do heréi e fundo rolando em parallax Neste ponto, o fundo ja se movimenta juntamente com a nave ¢ os inimi- gos (figura 73). A partir de agora, como trabalharemos sempre com um fundo rolando, nao é preciso mais realizar a limpeza de tela a cada ciclo da animagao, bastando apenas desenhar o fundo para cobrir a tela. Isso nao trard efeitos praticos mas economizara algum processamento. Por isso, vamos comentar a linha que limpa a tela no método proximoFrame da classe Animacao. proximoFrane: function() { // Posso continuar? if (! this.ligado ) return; // Comente o comando que limpa a tela // this. LimparTelaQ ; Obs.: recomendo que vocé faga uma cépia do arquivo animacac. is 138 Casa do Codigo Capitulo 7. Criando inimigos especialmente para este t6pico. Do contrario, testes anteriores serao afetados (figura 7.4). Para os proximos capitulos, vocé poder usar a nova versio dese arquivo. PN Figura 7.4: Mantenha a limpeza de tela para os testes anteriores, caso contrario sero prejudicados. Experimente rodar o aplicativo. J4 temos um protétipo de jogo quase pronto! 7.4 ADICIONANDO COLISAO A parte que falta para termos um prototipo funcional ¢ detectar a colisao entre os sprites — mais exatamente, entre o inimigo e a nave (causando a morte do herdi) e entre os inimigos e 0 tiro (causando a destruigao do inimigo). Precisamos, em algum ponto de nosso programa, chamar 0 método processar do Colisor. Que tal sea Animacao chamar o colisor em cada ciclo? Assim, teremos um controle mais geral, que ocorre em conjunto com o processamento dos ciclos de animagio. 139 24. Adicionando colisio Casa do Cédigo Indo mais além, a Animacao poderia manter uma lista de processamen- tos a executar, dos quais um ¢0 Colisor. Mais adiante, podemos adicionar Gravidade, TelaMovel e por ai vai. Fagamos isto! No construtor da classe Animacao, adicione 0 comando que inicia um array vazio processamentos: this.processamentos = []; Crieo método novoProcessamento, para inserir novos processamen- tos no array: novoProcessamento: function(processamento) { this. processamentos . push (processamento) ; processamento.animacao = this; No método proximoPrame, vamos executar estes processamentos: proximoFrame: function() { // Posso continuar? if (! this.ligado ) return; // Rtuallizamos 0 estado dos sprites for (var i in this.sprites) this. sprites [i] .atualizar(); // Desenhamos os sprites for (var i in this.sprites) this. sprites [i] .desenhar(); // Processamentos gerais for (var i in this.processamentos) this. processamentos [i] processar() ; // Chamamos o préximo ciclo var animacao = this; requestAnimationFrame(function() { animacao. proximoFrame() ; ns 1, 140 Casa do Codigo Capitulo 7. Criando inimigos Iniciando o teste Ja temos o arquivo teste-inimigos-e-fundo.html, com tudo o que foi feito até aqui. Faca uma cépia e dé o nome teste-co: sa0-inimigos.html. Localize 0 ponto onde o colisor € criado e acrescente-o como um processamento da animaga var colisor = new Colisor(); colisor.novoSprite (nave) ; animacao.novoProcessamento(colisor) ; Os inimigos ja estao sendo adicionados no colisor, porém faltam os tiros. Eles sao criados no método at irar da Nave. Modifique-o para que o tiro criado seja incluido no colisor: atirar: function() { var t = new Tiro(this.context, this); this. animacao.novoSprite(t) ; this. colisor.novoSprite(t) ; Mas... a nave tem referéncia ao colisor? Seria bom que cada sprite adi- cionado no colisor tivesse uma referéncia a este (assim como a Animacao ja faz). Modifique o método novosprite do Colisor para fazer isso: novoSprite: function(sprite) { this. sprites. push(sprite) ; sprite.colisor = this; }, 14 24. Adicionando colisio Casa do Cédigo REFERENCIAS CRUZADAS. Vamos adotar a pratica de, em qualquer objeto que agregue e processe varios outros objetos (Animacao, Colisor ete.), colocar neles uma referéncia ao objeto agregador. Por exemplo: // kdiciono um sprite ao colisor colisor.novoSprite(carro) ; /f 0 coliser adiciona-se a si meamo no sprite novoSprite: function(sprite) { this. sprites. push(sprite) ; sprite.colisor = this; }, Adaptando os sprites a interface do colisor Neste ponto, se tentarmos rodar 0 aplicativo, a animacio ira parar abrup- tamente e teremos a seguinte mensagem no console ( Ctr1+Shift+J no Google Chrome, ct r1+Shift+K no Firefox): Elements Resources Network Sources Timeline Profiles Audits | Console) @ Uncaught TypeError: Object #aobject> has no method ‘retangulcscolisaa Figura 75: Tentando chamar um método que nao existe no objeto Isso ocorreu porque agora o colisor esté sendo chamado, e este, por sua vez, chama os métodos retanguloColisao e colidiuCom. Precisamos implementé-los nas classes Nave, Tiro, e Ovni. Faga isto nas trés, por enquanto deixando os corpos dos métodos vazios: retangulosColisao: function() { 142 Capitulo 7. Criando inimigos colidiuCom: function(outro) { Agora o aplicativo voltou a funcionar como a versio anterior. Vamos definir os retangulos de coliséo dos sprites. O mais facil é 0 do Tiro, que ja é retangular: retangulosColisao: function() { return [ {x: this.x, y: this.y, largura: this.largura, altura: this.altura} 1; }, Para a nave, vamos fazer trés reténgulos, para o corpo e as duas asas (figura 7.6). Nao ha uma regra fixa de quantos retangulos usar, vocé define a partir do formato de seu objeto. No caso de objetos mais complexos, é in- teressante desenhar os retangulos na tela, a fim de podermos ajustar os valores das posicGes mais facilmente: retangulosColisao: function() { // Estes valores véo sendo ajustados aos poucos var rets = C {x: this.x#2, y: this.y#19, largura: 9, altura: 13}, {x: this.x#13, y: this.y+3, largura: 10, altura: 33}, {x: this.x+25, y: this.y+19, largura: 9, altura: 13} 13 // Desenhando os reténgulos para visualizagéo var ctx = this.context; for (var i in rets) { ctx. save(); ctx, strokeStyle = ‘yellow’; ctx. strokeRect (rets[i].x, rets[i].y, rets[i].largura, rets[i] .altura) ; ctz.restore(); 143 24. Adicionando colisio Casa do Cédigo return rets; 1, Figura 7.6: Retingulos de coliséo da nave Para analisar os retangulos de sprites em movimento, facilita muito abrir o console ( ct r1+shift+s no Chrome; no Firefox instale o plugin Firebug) echamar o comando: animacac.desligar(): Elements Resources Network > animacao.desligar() undefin >| Figura 77: Pare aanimagio pelo console para analisar os retangulos de colisio Para o OVNI, também usei trés retangulos: retangulosColisao: function() { var rets = : this.x+20, y: this.y+1, largura: 25, altura: 10}, : this.x+2, y: this.y+11, largura: 60, altura: 123, this.x+20, y: this.y+23, largura: 25, altura: 7}, 144 Casa do Codigo Capitulo 7. Criando inimigos // Desenho dos retangulos .. Figura 7.8: Retingulos de colisio do OVNI Rode o aplicativo e dé alguns tires. Vocé rapidamente percebera um prob- lema. 75 ESTAMOS EXPERIMENTANDO LENTIDAO! Nosso jogo chegou em um ponto em que muitos objetos estio sendo criados, mas nio esto sendo destruidos! Agora, além do loop de animacio, eles também devem responder as chamadas do colisor. Isso, claro, esta sobrecar- regando 0 aplicativo. 145 25. Estamos experimentando lentidio! Casa do Cédigo Figura 7.9: Experimente dar muitos tiros: nosso jogo ficara bem lento! Nosso game engine, embora tenha funcionado perfeitamente até aqui, carece de algo bem basico: nao é possivel excluir objetos, tanto da animagio quanto do colisor. Estamos sempre criando novos tiros e inimigos, mas no os destruimos quando estes deixam a tela. Resultado: continuam sendo pro- cessados. O que precisamos para excluir tiros e inimigos que ja se foram da tela? Importante: siga este topico com bastante atengio, pois faremos alter- es delicadas no game engine. Procure sempre fazer backups de todos os arquivos. Problema na exclusao de elementos Antes de criar 0 método excluirsprite nas classes. Animacao e Coliser, preciso contar-Ihe uma coisa. Quando estava desenvolvendo 0 protétipo de jogo para este livro, ao implementar a exclusio de itens dos ar- rays, eu inocentemente mandei excluir o elemento tio logo que ele saia da tela: // Exemplo 146 Casa do Codigo Capitulo 7. Criando inimigos // Wao faga isto! atualizar: function() { // Sprite sumiu da tela if (this.y > this.context.canvas.height) { this. animacao.excluirSprite(this) ; this. colisor.excluirSprite(this); E um cédigo simples e claro, mas que provocou um bug que levei horas para descobrir 0 que era. Normalmente eu tenho deixado os bugs aparecerem para discutir a solugio no préprio caminhar do livro, mas desta vez no vou fazer isso com vocé. Vou poupi-lo deste aqui. A explicacao é que estamos excluindo elementos de um array dentro do loop que percorre esse array! Mas onde esté esse loop? Na classe Animacao, método proximoFrame, temos varios loops que chamam os métodos atualizar, desenhareo processar do colisor, sendo que este faz outro loop em seu array! // Trecho do método proximoFrame da classe Animacao // ktualizamos o estado dos sprites for (var i in this.sprites) this. sprites [i] .atualizar(); // Desenhamos os sprites for (var i in this.sprites) this. sprites [i] .desenhar(); // Processamentos gerais for (var i in this.processamentos) this. processamentos [i] .processar (); Se, por exemplo, eu mando excluir um item de dentro do método atualizar, o loop que chamao atualizar ficard 6rfao daquele item! Como regra geral, vocé nao deve excluir itens de um array dentro do loop que o percorre: 147 25. Estamos experimentando lentidio! Casa do Cédigo // Exemplo teérico // Yoc8 nao deve fazer isto // Criar um array var meudrray = ['banana', ‘laranja', '1iméo']; // Fazer um loop for (var i in meudrray) { // Forgar uma exclusao dentro do loop if (mewArray[i] == ‘laranja') excluirElemento(meuArray[i], meudrray) ; // Fazer alguma tarefa com o array document .write(meudrray[i] + '
'); A solucao? Manter uma lista de objetos a excluir, e exclui-los somente ao fim do ciclo de animagao! Excluindo elementos dos arrays ‘Vamos entio implementar a exclusao de elementos com bastante calma. Desta forma, eliminamos o problema da lentidao e poderemos, enfim, tratar as colisdes, fazendo o inimigo ¢ o tiro sumirem quando se chocarem, por exemplo, ‘Temos que iniciar as listas de elementos a serem excluidos. No construtor da classe Colisor, faga: this.spritesExcluir = (1; Paraa classe Animacao, ha dois arrays: this.spritesExcluir = []; this.processamentosExcluir = []; © método excluirsprite apenas incluird o sprite nessa lista. No co! ‘or, temos: excluirSprite: function(sprite) { this. spritesExcluir.push(sprite) ; 148 Casa do Codigo Capitulo 7. Criando inimigos Na Animacao, temos que ter excluirSprite e excluirProcessamento: excluirSprite: function(sprite) { this. spritesExcluir.push(sprite) ; }, excluirProcessamento: function(processamento) { this. processamentosExcluir .push (processamento) ; No fim do método processar do Colisor, colocamos uma chamada a processarExclusoes: processar: function() { “ this. processarExclusoes(); 3, E vamos criar esse método processarExclusoes. Como pode haver varios sprites a seem eliminados, vamos na verdade montar um novo array contendo todos os elementos, menos aqueles que foram excluidos. O array antigo poder4 ser descartado e ficard livre para 0 coletor de lixo (garbage col- Iector) do JavaScript apaga-lo da meméria: processarExclusoes: function() { // Criar um novo array var novoArray = []; // Adicionar somente os elementos nfo excluidos for (var i in this.sprites) { if (this.spritesExcluir. index0f(this.sprites[i]) novoArray.push(this.sprites[i]) ; // Limpar o array de exclusées this. spritesExcluir - [1; // Substituir 0 array velho pelo novo 149 25. Estamos experimentando lentidio! Casa do Cédigo this. sprites - novoArray; Para a Animacao, 0 procedimento é muito semelhante. No fim do método proximoFrame, logo antes da chamada do préximo ciclo, chame também 0 método processar=xclusoes: proximoFramé “t function() { // Processamento de exclus this. processarExclusoes() ; // Chamamos o préximo ciclo var animacao = this; requestAnimationFrame(function() { animacao. proximoFrame() ; Hs 3, E vamos criar esse método. Fle segue o mesmo algoritmo de seu equiva- lente no Colisor, s6é um pouco maior porque estamos lidando com dois arrays. processarExclusoes: function() { // Criar novos arrays var novoSprites = []; var novoProcessamentos = []; // Adicionar somente se ndo constar no array de excluidos for (var i in this.sprites) { if (this.spritesExcluir.indexOf(this.sprites[i]) == -1) novoSprites.push(this.sprites[i]); for (var i in this.processamentos) { if (this.processamentosExcluir. index0f (chis.processamentos[i]) == -1) novoProcessamentos .push(this.processamentos[i]) 3 150 Casa do Codigo Capitulo 7. Criando inimigos // Limpar os arrays de exclusées this. spritesExcluir = []; this.processamentosExcluir = [1]; // Substituir os arrays velhos pelos novos this. sprites - novoSprites; this.processamentos = novoProcessamentos; A exclusio esta implementada! Esta parte foi fogo, néo foi? Mas agora podemos facilmente mandar excluir objetos que estio sobrando na meméria, para que eles no provoquem mais lentidao! 7.6 EXCLUINDO OS OBJETOS DESNECESSARIOS Vamos comegar pelo Tiro. Quando sumir da tela, ele deve ser excluido. Em seu método atualizar, verificamos sua posigao y eo tiramos da animagao e do colisor quando sua posigio for negativa (excedendo a borda do Canvas): atualizar: function() { this.y -= this. velocidade; // Exeluir 0 tiro quando sumir da tela if (this.y < -this.altura) { this. animacao.excluirSprite(this) ; this. colisor.excluirSprite (this) 5 }, O Ovni também deve ser excluido, mas como seu movimento é em sen- tido contrario, ele deve passar da altura do Canvas: atualizar: function() { this.y ++ this. velocidade; if (this.y > this.context.canvas.height) { this. animacao.excluirSprite(this); 151 74. Excluindo os objetos desnecessirios Casa do Cédigo this. colisor.excluirSprite (this) ; 1, Também, tanto 6 Tixo quanto o Ovni devem ser excluidos em caso de colisdo entre eles. Afinal, esta é uma regra de negécio dbvia de nosso jogo! Podemos implementar isto no método col idiuCom de qualquer uma destas classes. Escolhi a classe Ovni, conferindo se o outro objeto é um Tiro: colidiuCom: function(outro) { // Se colidiu com um Tiro, os dois desaparecen if (outro instanceof Tiro) { this. animacao. excluirsprite (this); this. colisor.excluirSprite(this) ; this. animacao. excluirSprite(outro) ; this. colisor.excluirSprite (outro); Por iiltimo, a colisdo entre a Nave eo Ovni. Nosso herdi, que era in- vencivel, agora pode morrer! Programe na classe Nave: colidiuCom: function(outro) { // Se colidin com um Ovni... if (outro instanceof Ovni) { // Fim de jogo! this. animacao. desligar() ; alert('GAME OVER"); Este € um momento para se comemorar! Se vocé fez todas as tarefas até aqui com calma e atengio, vocé tem agora um game engine maduro para uso e um exemplo de jogo totalmente funcional, embora ainda nao esteja pronto. Experimente jogar. Ja é possivel destruir os inimigos e ser atingido por eles. No préximo capitulo, realizaremos este e outros diversos ajustes que deixarao nosso jogo com aspecto profisisonal. Exercicio: tente fazer a nave sofrer 3 colisdes antes de ser destruida. Para isso, basta criar um atributo (sugestoes de nome: energia, pontosVida) 152 1a do Codigo Capitulo 7. Criando inimigos e ir decrementando seu valor a cada colisio. Quando chegar a zero... GAME OVER! 153, CapiruLo 8 Incorpore animac6es, sons, pausa e vidas extras ao jogo Uma boa noticia: neste capitulo, nosso jogo de nave sera concluido! J4 temos um prototipo totalmente funcional, mas ainda ha muito o que ser melhorado para dar-lhe um aspecto profissional. Serao feitas as seguintes melhorias: + Organizacao do cédigo: nosso jogo esta crescendo e, por isso, a pagina inicial vai comecar a ficar um pouco longa e desorganizada. Faremos algumas refatoragées para acomodar mais facilmente as mu- dangas que esto por vir. + Animagio cronometrada: para a animacao correr a velocidades con- stantes, e nao no ritmo da CPU (do jeito que esti, a velocidade da ani- magio podera oscilar muito). 8.1. Organizando 0 eédigo Casa do Cédigo + Uso de spritesheets: nés aprendemos como usé-las no capitulo 4, mas nao aplicamos ainda em nosso jogo! Com elas, criaremos explosées e daremos animagio & nave, deixando nosso jogo muito mais interes- sante. + Pausa: como seria a experiéncia de jogar um jogo sem poder pausé-lo? Péssima, nao é mesmo? + Som e musica de fundo: vocé nio estava sentindo a falta disto, nao? + Telas de loading e Game Over: um jogo utiliza muitas imagens. Quando vocé hospedé-lo na internet, nao vai querer que o usuario veja uma tela parada enquanto as imagens carregam, nao é verdade?. Um aviso “Carregando..” e uma barrinha aumentando na tela fardo com que o internauta nio desista do seu jogo; ao contrério, aumentard sua expectatival E uma tela que indica quando o jogo acabou, cairé muito bem. + Vidas extras: por que o jogo tem que acabar na primeira colisio da nave? Vamos dar mais chances ao jogador! + Pontuacio (score): esta simples regra de negécio sera bem facil de implementar. Primeiro, vamos organizar todo o nosso trabalho. Seré uma excelente oportunidade de revisiio. Maos a obra! 8.1. ORGANIZANDO 0 CODIGO No capitulo anterior, 0 cédigo da pagina HTML de nosso jogo ficou um tanto extenso, pois juntamos quase tudo o que aprendemos em um tinico ar- quivo funcional. Considero esta uma boa hora para refatorar esse cédigo (e praticar um pouco também), deixando-o um pouco mais organizado e facil para acrescentar as melhorias que estao por vir. Crie uma nova pagina HTML. Os arquivos de script devem ser copiados de suas vers6es mais atualizadas, 156 Casa do Codigo Capitulo 8. Incorpore animagdes, sons, pausae vidas exteas a0 jogo arquivo: jogo-definitivo.html --> Jogo de Nave ‘Vamos continuar executando as tarefas nesta ordem: carregar as imagens, iniciar os objetos, iniciar a animagao. Que tal criar fungdes separadas? No JavaScript, faca: // Canvas e Context var canvas = document:. getElementById( 'canvas_animacao') ; var context = canvas. getContext('2d'); // Variaveis principais var imagens, animacao, teclado, colisor, nave, criadorInimigos; var totallmagens = 0, carregadas = 0; // Comega carregando as imagens 157 8.1. Organizando 0 eédigo Casa do Cédigo carregarImagens(); Na fungio carregar Imagens, facilita muito carregar um objeto ou ar- ray com os nomes das imagens e fazer um loop nele. Optei por usar um ob- jeto, pois assim posso associar cada imagem a seu nome (em ver de usar um ntimero sem significado). Apés carregar cada imagem, o nome é substituido pelo objeto da imagem: function carregarImagens() { // Objeto contendo os nomes das imagens imagens = { espaco: —' fundo-espaco.png', estrelas: 'fundo-estrelas.png’ ‘fundo-nuvens .png', 'nave.png', ‘ovni-png' 3 // Carregar todas for (var i in imagens) { var img = new Image(); img.src = 'img/' + imagens[i]; img.onload = carregando; total Imagens++; // Substituir o nome pela imagem imagens [i] = img; ‘Temos entio que criar aja conhecida fungio carregando, que vai moni- torar o carregamento das imagens ¢ iniciar a criagao dos objetos quando todas estiverem prontas: function carregando() { carregadas++; if (carregadas totalImagens) iniciarObjetos(); 158 Casa do Codigo Capitulo 8. Incorpore animagdes, sons, pausae vidas exteas a0 jogo A fungio iniciarobjetos iniciard os principais objetos do jogo, da forma como vocé jé est acostumado a fazer: function iniciarObjetos() { // Objetos principais animacao = new Animacao(context) ; teclado = new Teclado(document) ; colisor = new Colisor(); espaco = new Fundo(context, imagens. espaco) ; estrelas = new Fundo(context, imagens.estrelas) ; nuvens = new Fundo(context, imagens.nuvens); nave = new Nave(context, teclado, imagens.nave) ; // Ligagdes entre objetos animacao.novoSprite(espaco) ; animacao. novoSprite(estrelas) ; animacao. novoSprite(nuvens) ; animacao. novoSprite(nave) ; colisor.novoSprite (nave) ; animacao. novoProcessamento (colisor) ; configuracoesIniciais(); Foi chamada a fungdo configuracoesIniciais, que configura as velocidades dos fundos, posiciona a nave, configura o disparo pela tecla Espago e inicia a animacio: function configuracoesIniciais() { // Fundos espaco.velocidade = estrelas. velocidade nuvens.velocidade = 1 // Nave nave.x = canvas.width / 2 - imagens.nave.width / 2; nave.y = canvas.height - imagens.nave.height; nave. velocidade = 5; 159 8.1. Organizando 0 eédigo Casa do Cédigo // Tixo teclado.disparou(ESPACO, function() { nave.atirar(); ns animacao. ligar(); Faga o teste: neste ponto vocé ja deve tera nave controlavel e atirando, eo fundo rolando. Vamos agora configurar a criacao dos inimigos como um pro- cessamento da animagao. Ao final da fungéo configuracoesIniciais, acrescente a chamadaa criacaoInimigos: function configuracoesIniciais() { “ criacaoInimigos(); E vamos criar essa fungio. Ela cria um objeto sem construtor, com 0 método processar, ¢o insere como um processamento geral na animagao: function criacaoInimigos() { criadorInimigos = { processar: function() { animacao.novoProcessamento(criadorInimigos) ; No método processar desse objeto, criaremos um inimigo a cada se- gundo. Mas, para isso, necessitamos saber o instante em que o iiltimo inimigo foi gerado. Este instante é guardado no atributo ultimoovni e atualizado quando 0 tempo decorrido ultrapassar 1000 milissegundos: function criacaoInimigos() { var criador = { 160 Casa do Codigo Capitulo 8. Incorpore animagdes, sons, pausae vidas exteas a0 jogo ultimofvni: new Date().getTime(), processar: function() { var agora = new Date().getTime(); var decorrido = agora - this.ultimo0vni; if (decorrido > 1000) { novo0vni() ; this.ultimoOvni = agora; animacao.novoProcessamento(criador) ; Se passar um segundo desde a geracao do tiltimo inimigo, é chamada a fungio novoovni, que é muito semelhante a do capitulo anterior. Ela gera inimigos em posigdes e com velocidades aleatérias. Se nao lembra como fazer isso, consulte 0 tdpico 7.1. function novoOvni() { var imgOvni = imagens.ovni; var ovni = new Ovni(context, imgOvni); // Minimo: 5; maximo: 20 ovni.velocidade = Math.floor( 5 + Math.random() * (20 - 5 + 1) ); // Minimo: 05 // m&ximo: largura do canvas - largura do ovni ovni.x = Math. floor(Math.random() + (canvas.width - img0vni.width + 1) ); // Descontar a altura ovni.y = -imgOvni.height; animacao.novoSprite(ovni); 161 82. Animagao cronometrada Casa do Cédigo colisor.novoSprite (omni) ; Agora estamos muito mais organizados, com cada etapa da inicializacio do jogo em uma fungao especifica. Estamos prontos para implementar as novidades. 8.2 ANIMACAO CRONOMETRADA, Para dar um aspecto mais profissional a nosso jogo, devemos cronometrar as animages. Vocé pode ter notado que, as vezes, a velocidade oscila. Isto é comum em browsers e jogos que rodam em sistemas operacionais multitarefa: a CPU pode estar ocupada enquanto 0 jogo espera 0 momento de processar © proximo ciclo. Para resolver isso, precisamos primeiro saber 0 tempo decorrido entre um ciclo e outro. No construtor da classe Animacao, crie os atributos ultimoCiclo (para guardar o instante do ciclo anterior, lido do relégio) decorrido (para guardar o tempo decorrido entre 0 ciclo anterior € 0 atual): function Animacao(context) { “ this.ultimoCiclo = 0; this.decorrido = 0; Para fazer os cAlculos, obtemos o instante atual do relogio do computador (dado por Date. get Time () em milissegundos) e calculamos a diferenca entre esse instante ¢ 0 instante do ciclo anterior. Faremos isso no método proximoFrame, logo antes de processar os sprites: proximoFrame: function() { // Posso continuar? if (! this.ligado ) return; var agora = new Date().getTime(); if (this.ultimoCiclo 0) this.ultimoCiclo = agora; this.decorrido = agora - this.ultimoCiclo; 162 Casa do Codigo Capitulo 8. Incorpore animagdes, sons, pausae vidas exteas a0 jogo Mt No fim do método proximoFrame, atualize o atributo ultimoCiclo, imediatamente antes de chamar o proximo ciclo: proximoFrame: function() { Mt // ktualizar o instante do di1timo ciclo this.ultimoCiclo = agora; // Chamamos 0 préximo ciclo var animacao = this; requestAnimationFrame(function() { animacao.proximoFrame() 5 Hs 3, Agora os sprites sabem quanto tempo levou entre um ciclo e outro! Vamos fazer 0 Fundo mover-se a uma velocidade constante. Para isso, modifique o método atualizar: atualizar: function() { // Atualizar a posigéo de emenda this. posicaoEmenda += this. velocidade * this.animacao.decorrido / 1000; Mt 3, 163 82. Animagao cronometrada Casa do Cédigo FORMULA PARA ANIMAGAO CRONOMETRADA. incremento da posigio do sprite, em pixels, é dado pela formula: velocidade + tempoDecorrido / 1000 Sendo: + velocidade em pixels por segundo; + tempoDecorrido em segundos (como o tempo dado por Date.getTime () & em milissegundos, dividimos esse valor por 1000) Note que agora o fundo move-se em velocidade constante, porém bem devagar, pois passamos a trabalhar com pixels por segundo. Podemos ajustar novas velocidades com valores maiores na fungio configuracoesIniciais da pagina HTML. Vocé pode fazer varios testes e atribuir os valores que desejar, dependendo da sensacio de velocidade que quer passar. Como as nuvens estéo mais proximas, dei a elas a maior velocidade, mas nada impede que coloquemos as estrelas em primeiro plano, por exemplo: function configuraccesIniciais() { // Fundos espaco. velocidade = 60; estrelas.velocidade = 150; muvens.velocidade = 500; “ Vamos cronometrar também o movimento da Nave. Aplique a formula em seu método atualizar: atualizar: function() { var incremento = 164 Casa do Codigo Capitulo 8. Incorpore animagdes, sons, pausae vidas exteas a0 jogo this.velocidade * this.animacao.decorrido / 1000; // Use a variavel incremento em todas as mudangas de x e y if (this. teclado.pressionada(SETA_ESQUERDA) kk this.x > 0) this.x -= incremento; WW + Na pagina HTML, dei a ela a velocidade de 200 pixels por segundo: // Nave nave.x = canvas.width / 2 - imagens.nave.width / 2; nave.y = canvas.height - imagens .nave.height; 200; nave. velocidade Exercicio: faga vocé mesmo 0 ajuste para o Ovni eo Tiro! Dé-lhes as velocidades que desejar. A do Tiro é configurada no construtor, e as dos ovnis, no método novoovni da pagina. Dica: no pacote de download, a pagina jd esté com a solugio implementada. 8.3. ANIMANDO A NAVE COM SPRITESHEETS No pacote de download do livro h 0 arquivo nave-spritesheet.pna (figura 8.1). Iremos usé-lo para melhorar 0 aspecto de nossa nave. Conven- hamos, ela est muito parada... nem o fogo em sua cauda se mexe! 165 83. Animando a nave com spritesheets Casa do Cédigo Ab sth v/joodyy Figura 8.1: Spritesheet para a nave No capitulo 4, definimos que as linhas da spritesheet representam difer- entes estados do sprite. Aqui temos a nave parada, movendo-se para a es- querda e movendo-se para a direita. Em uma linha, a animagao ocorre avangando as colunas. Em cada uma destas linhas, ha duas colunas que ani- mam o fogo na cauda Iniciando 0 teste Para comegar a programar com spritesheets, primeiro acrescente a refer- éncia ao arquivo spritesheet . 4s, criado no capitulo 4: Na fungio carregar Imagens, mude a imagem da nave para o arquivo nave-spritesheet .png: imagens = { iy nave: ‘nave-spritesheet.png', Mt Agora nao podemos mais usar as dimens6es da imagem para posicionar anave. Em configuracoesiniciais, vamos passar valores absolutos ref- erentes a cada quadro (considerando que cada nave tem 36x48 pixels): 166 Casa do Codigo Capitulo 8. Incorpore animagdes, sons, pausae vidas exteas a0 jogo function configuracoesIniciais() { Mt // Nave nave.x = canvas.width / 2-18; // 36 / 2 nave.y = canvas.height - 48; nave. velocidade = 5; Mt No construtor da classe Nave, vamos iniciar 0 objeto que controla a spritesheet. Usaremos inicialmente a linha zero, que representa a nave parada. O intervalo entre um quadro e outro pode ser ajustado aos poucos conforme © seu gosto: function Nave(context, teclado, imagem) { Ms. this. spritesheet = new Spritesheet (context, imagem, 3, 2) this. spritesheet.linha = 0; this. spritesheet.intervalo = 100; Altere agora o método desenhar para usar a spritesheet. Para definir qual a linha a ser animada, lemos 0 estado das setas do teclado: desenhar: function() { if (this. teclado. pressionada(SETA_ESQUERDA)) this. spritesheet.linha = 1; else if (this. teclado.pressionada(SETA_DIREITA)) this. spritesheet.linha = 2; else this. spritesheet.1inha " ° this. spritesheet.desenhar(this.x, this.y); this. spritesheet . proximouadro() ; 3, ‘Também precisamos alterar 0 método atualizar, pois ele também est usando as dimensdes da imagem para nao deixé-la passar da borda 167 83. Animando a nave com spritesheets Casa do Cédigo do Canvas. Nés substituimos as referéncias a this.imagem.width e this. imagem. height pelos valores absolutos 36 e 48, respectivamente: atualizar: function() { ut if (this. teclado.pressionada(SETA_DIREITA) && this.x < this.context.canvas.width - 36) this.x += this.velocidade; “uh if (this. teclado. pressionada(SETA_ABAIXO) && this.y < this.context.canvas.height - 48) this.y += this.velocidade; i, Por ultimo, perceba que o tiro sai um pouco deslocado, pois sua posigao 6 calculada pelo tamanho da imagem antiga, Vamos ajustar para um valor absoluto, no construtor da classe Tixo. Também aproveitei o momento para mudar a cor e deixé-lo um pouco menor function Tiro(context, nave) { this. context = context; this.nave = nave; // Posicionar 0 tiro no bico da nave this.largura = 3; this.altura = 10; this.x = nave.x +18; // 36 / 2 this.y = nave.y - this.altura; 103 this. velocidade this.cor = ‘yellow’; Experimente jogar e perceba que o movimento da nave ganhou mais di- namismo! 168 Casa do Codigo Capitulo 8. Incorpore animagdes, sons, pausae vidas exteas a0 jogo 8.4 CRIANDO EXPLOSOES Vamos aproveitar 0 embalo das spritesheets e criar exploses. Usaremos 0 arquivo explosao .png, que contém a spritesheet na figura 8.2: 0 GON Ss Figura 8.2: Spritesheet para explosio Também sera preciso acrescentar uma referéncia ao script explosao. js, ser criado logo mais: Na fungo carregarImagens, acrescente a imagem da explosio: imagens = { “ ovni: ‘ovni.png', // uma virgula aqui explosao: 'explosao.png' No método colidiucom da classe ovni, onde este é destruido pelo Tixo, vamos criar uma explosio: colidiuCom: function(outro) { // Se colidin com um Tiro, os dois desaparecem if (outro instanceof Tiro) { we var explosao = new Explosao(this. context, this. imgExplosao, this.x, this.y); this. animacao.novoSprite(explosao) ; 169 8.4. Criando explosies Casa do Cédigo Os objetos que explodem precisam receber a imagem da explosio, para poder criar os sprites. Mude o construtor do Ovni: function Ovni(context, imagem, imgExplosao) { i this. imgExplosao = imgExplosao; E passe a imagem na fung’o novoovni da pagina HTML: var ovni = new Ovni(context, img0vni, imagens.explosao); Agora, crie a classe Explosao no arquivo explosao. 3s: function Explosao(context, imagem, x, y) { this.context = context; this. imagem = imagem; this. spritesheet = new Spritesheet (context, imagem, 1, 5); this.spritesheet.intervalo = 75; this.x = x; this.y = y3 } Explosao.prototype = { atualizar: function { }, desenhar: function() { } + No método desenhar, nés desenhamos o quadro atual e animamos a spritesheet: desenhar: function() { this. spritesheet.desenhar(this.x, this.y); this. spritesheet.proximoQuadro() ; Mas assim a explosao ficara piscando na tela eternamente! O que quer- emos é que, quando todos os quadros forem exibidos, a explosio termine. 170 Casa do Codigo Capitulo 8. Incorpore animagdes, sons, pausae vidas exteas a0 jogo Mas como saber se a spritesheet chegou ao Ultimo quadro? Vamos criar nela uma funcionalidade com a qual ela prépria avisa isto. No construtor da Explosao, crie um callback que recebe esse aviso: function Explosao(context, imagem, x, y) { Mt var explosao = this; this. spritesheet.fimDoCiclo = function() { explosao. animacao.excluirSprite(explosao) ; Agora crie o atributo £imDoCiclo no construtor da spritesheet: function Spritesheet (context, imagem, linhas, colunas) { “ this. fimDoCiclo = null; E modifique 0 método proximoQuadzo () para chamar esse callback quando voltar ao quadro zero: proximoQuadro: function() { Mt if (this.coluna < this.numColunas - 1) { this. colunat+; } else { this. coluna // Avisar que acabou um ciclo! if (this.fimDoCiclo) this.fimDoCiclo(); “ i, Experimente jogar! © Ovni jé explode ao ser atingido pelo tiro. Agora vamos explodir tanto a Nave quanto 0 Ovni quando eles colidirem. No método colidiuCom da Nave, vamos criar duas explosées: wy 8.4. Criando explosies Casa do Cédigo colidiuCom: function(outro) { // Se colidiu com um Ovni. if (outro instanceof Ovni) { this. animacao. excluirSprite(this); this. animacao. excluirSprite (outro) ; this. colisor.excluirSprite(this) ; this. colisor.excluirSprite (outro) ; var expl = new Explosao(this.context, this. imgExplosao, this.x, this.y); var exp2 = new Explosao(this.context, this. outro.x, outro.y); mgExplosao, this. animacao. novoSprite(exp1) ; this. animacao. novoSprite(exp2) 5 // Por enquanto tire o término da animagéo Claro, a Nave também precisard receber a imagem da explosao no con- strutor: function Nave(context, teclado, imagem, imgExplosao) { Mo. this. imgExplosao = imgExplosao; Portanto, passe-a ao criar a nave (método iniciazObjetos da pagina HTML): nave = new Nave(context, teclado, imagens.nave, imagens. explosao) ; A Nave € 0 Ovni jé explodem juntos. Gostariamos de parar a ani- maco e dar a mensagem “GAME OVER” somente quando essas explosées finalizarem. Portanto, o sprite Explosao também poderia receber um call- back. Complete o colidiucomda Nave para passar esse callback: 172, 1a do Codigo Capitulo 8. Incorpore animagdes, sons, pausae vidas exteas a0 jogo colidiuCom: function(outro) { // Se colidiu com um Ovni... if (outro instanceof Ovni) { Uf wae expi.fimDaExplosao = function() { animacao.desligar(); alert('GAME OVER'); E modifique o construtor da £xp1osao para chamar esse callback ao fim do ciclo da spritesheet: function Explosao(context, imagem, x, y) { “it var explosao = this; this. fimDaExplosao = mill; this. spritesheet.fimDoCiclo = function() { explosao. animacao.excluirSprite(explosao) ; if (explosao.fimDaExplosao) explosao. fimDaExplosao(); Nao élegal? A Spritesheet informa A £xp1osao quando completou a animagao dos quadros, ea Explosao se retira da animagao e notifica a quem interessar. Brinque um pouco, acho que vocé merece! 173 8.4. Criando explosies Casa do Cédigo Figura 8,3: Agora as colisdes tém emogio! 174 1a do Codigo Capitulo 8. Incorpore animagdes, sons, pausae vidas exteas a0 jogo 8.5 PAUSANDO 0 JOGO Figura 8.4: Jogo pausado com indicativo na tela ‘Nés jé temos pausa implementada em nosso game engine: os métodos Ligar e desligar da classe Animacao. No entanto, é preciso tratar alguns acon- tecimentos que esto ocorrendo na animagao, ao se pausar 0 jogo. Vamos definir que o jogador pausard o jogo através da tecla Enter, cujo cédigo 613 (se preferir usar outra tecla, fique A vontade). No inicio do arquivo teclado . js, acrescente uma varidvel para guardar 0 cédigo do Enter: // Cédigos de teclas - aqui véo todos os que forem necessarios var SETA_ESQUERDA = 37; var SETA_ACIMA = 38; var SETA_DIREITA = 39; var SETA_ABAIXO = 40; 175 85, Pausando 0 jogo Casa do Cédigo var ESPACO = 325 var ENTER = 13; Agora, no método configuracoesIniciais, configure a agiodatecla Enter: // Pausa teclado.disparou(ENTER, pausarJogo) ; Ecriea fungio pausardJooo, que é quem fara o trabalho: function pausarJogo() { if (amimacao.1igado) animacao. desligar(); else animacao. ligar(); Faca o teste: neste ponto o jogo jd deve pausar e despausar. No entanto, vocé poder notar alguns bugs. Primeiro, pause enquanto um disco voador estd na tela, e em seguida despause: ele some! Isso ocorre porque sua ani- magao esta cronometrada, e o tempo da pausa esté sendo contado para cal- cular seu movimento. Quando a animagio reinicia, 0 disco ja deve estar longe, e assim 0 jogo o posiciona. Para resolver isso, na classe Animacao, no método liga, reinicie o atributo u1timoCiclo para zero. Isto fara a contagem de tempo ser reinici- ada: ligar: function() { this.ultimoCiclo = 05 this.ligado = true; this. proximoFrame() ; 3, Um outro problema é: fungdes de disparo (no caso, o tiro) continuam sendo possiveis! Isto nao ocorre com a movimentagio pelas setas, pois sio lidas no método atualizar da Nave, dentro do loop de animagao (que estaria pausado). Modifiquemos a fungao para desativar ou reativar a tecla Espago (tiro) conforme pausamos e despausamos 0 jogo: 176 1a do Codigo Capitulo 8. Incorpore animagdes, sons, pausae vidas exteas a0 jogo function pausarJogo() { if (amimacao.ligado) { animacao. desligar (); ativarTiro(false) ; } else { animacao. ligar(); ativarTiro(true) ; } A fungio ativarTiro vai modificar os eventos do teclado, configu- randoa tecla Espago: function ativarTiro(ativar) { if (ativar) { teclado.disparou(ESPACO, function() { nave.atirar(); Ys } else teclado.disparou(ESPACO, null); + Para nfo ficarmos com _cédigo configuracoestniciats, remova as linhas: // Remova estas linhas // Tiro teclado.disparou(ESPACO, function() { nave.atirar(); Ys E coloque esta no lugar: // Tiro ativarTiro(true) ; repetide, em ‘Também ocorre que um inimigo é gerado imediatamente apés a despausa, Ppois estamos gerando um inimigo por segundo e a pausa faz esse tempo se 77 85, Pausando 0 jogo Casa do Cédigo exceder. Antes de ligar a animacao, reinicie o momento da geragao do tiltimo inimigo para o instante atual, para evitar que isso ocorra: function pausarJogo() { a else { criadorInimigos.ultimoOvni = new Date().getTime(); animacao. ligar(); ativarTiro(true) ; Quantos detalhes uma simples pausa nos obriga a tratar! Para ficar mais interessante, vamos exibir 0 texto “Pausado” na tela: function pausarJogo() { if (animacao.ligado) { animacao. desligar(); ativarTiro(false) ; // Escrever "Pausado" context.save(); context. fillstyle = 'white'; context. strokeStyle = ‘black’; contert.font = '50px sans-serif! ; contert.fillText("Pausado", 160, 200); context. strokeText("Pausado", 160, 200); context. restore(); } else { criadorInimigos.ultimoOvni = new Date().getTime(); animacao. Ligar(); ativarTiro(true) ; } Use sua imaginacio! Coloque qualquer imagem, texto, ou combinacio dos dois. Pode até instanciar outra Animacao, colocar alguns sprites especi- ficos e exibir alguns movimentos durante a pausa. 178 Casa do Codigo Capitulo 8. Incorpore animagdes, sons, pausae vidas exteas a0 jogo 8.6 SONS E MUSICA DE FUNDO Esta parte é muito facil de implementar. O HTMLs, além do Canvas, possui uma API de dudio muito simples. No pacote de arquivos, na pasta deste capi- tulo ( 08), ha uma subpasta com o nome snd contendo alguns arquivos de som, em formato MP3. BANCOS DE SONS GRATIS E DIREITOS AUTORAIS Jamais use sons de terceiros sem a devida autorizagao! Criadores de jogos independentes, sem meios ou conhecimentos para gerar seus préprios sons, podem contar com intimeros bancos de sons gratuitos es- palhados pela internet. Os sons para o jogo deste livro foram obtidos em http://freesound.org. Primeiro, vamos produzir 0 som dos tiros. Abra 0 arquivo tiro. jse coloque as instruges para carregar 0 som logo no inicio do arquivo. Instan- ciamos um objeto Audio (presente na API do HTMLs), setamos seu atributo src para o arquivo t irc .mp3 (presente no pacote de download) e ajustamos o volume como um valor entre 0 ¢ 1. Por tiltimo, chamamos 0 método load para iniciar o carregamento do arquivo, evitando que seja carregado somente no momento da reprodugao: var SOM_TIRO = new Audio(); SOM_TIRO.sre = 'snd/tiro.mp3'; SOM_TIRO. volume = 0.2; SOM_TIRO. load() ; Depois, vamos reproduzir esse som cada vez que um tiro é criado. No construtor, rebobinamos 0 som ajustando 0 atributo currentTime para zero. Esse atributo indica o instante atual de reprodugio, dado em segundos. Em seguida, basta chamar 0 método play: function Tiro(context, nave) { HW 179 86, Sons e musica de Fundo Casa do Cédigo SOM_TIRO. currentTime = 0.0; SOM_TIRO. play() ; Os tiros jé fazem barulho! Vamos fazer 0 mesmo procedimento para as explosdes. No inicio do arquivo explosao. js, coloque as instrugées: var SOM_EXPLOSAQ = new Audio(); SOM_EXPLOSAO.sre = 'snd/explosao.mp3'; SOM_EXPLOSAO. volume = 0.4; SOM_EXPLOSAO.1oad() ; Eno construtor, mande rebobinar e reproduzir, da mesma forma que com o Tiro: function Explosao(context, imagem, x, y) { Wf SOM_EXPLOSAO. currentTime = 0.0; SOM_EXPLOSAO. play () ; Vamos também colocar uma miisica de fundo para dar mais emogao a jogo. No inicio da pagina HTML, acrescente a variével mus icaAcao: // Verigveis principais var imagens, animacao, teclado, colisor, nave, criadorInimigos; var totallmagens - 0, carregadas = 0; var musicaAcao; Em seguida, logo apés a chamada para carregarTmagens, coloque também uma chamada para carregarMusicas: // Comega carregando as imagens e misicas carregarImagens(); carregarHusicas(); Ecrieafungao carregarMusicas. Aqui, setamos oatributo Loop para txue, fazendo com que a miisica repita incessantemente durante a a¢ao do jogo. 180 Casa do Codigo Capitulo 8. Incorpore animagdes, sons, pausae vidas exteas a0 jogo function carregarMusicas() { musicaAcao = new Audio() ; musicaAcao.src = 'snd/musica-acao.mp3'; musicaAcao. load() 5 musicaAcao. volume = 0.8; musicaAcao.loop = true; musicaAcao. play(); + Viu como é bem facil? Divirta-se mais um pouco! Dica: vocé pode parar a miisica no momento da pausa usando 0 método pause. 8.7 TELA DE LOADING Pigura 8.5: Indique para 0 jogador que o jogo esta carregando 181 87 Telade loading Casa do Cédigo Vamos modificar a fungio carregando da pagina HTML para incrementar uma barra conformeas imagens vio sendo carregadas. Primeiro, como vamos desenhar, guardea configuragao atual do contexto (método save) e crie um fundo. Aqui, fiz um fundo simples, com a imagem do espaco. Se tiver vontade faga um desenho mais elaborado. Sinta-se livre! function carregando() { context. save(); // Fundo context. drawImage(imagens.espaco, 0, 0, canvas.width, canvas. height); // continua ... Em seguida, vamos criar o texto “Carregando": function carregando() { Ms // Texto "Carregando" context. fillstyle = ‘white’; contert.strokeStyle = ‘black! ; contert.font = '50px sans-serif'; context. fillText("Carregando...", 100, 200); context. strokeText ("Carregando...", 100, 200); // continua préximo passo é desenhar a barra. Neste ponto, precisamos incremen- tara varidvel carregadas, para poder calcular a largura atual da barra: function carregando() { Mt // Barra de loading carregadas++; 182, Casa do Codigo Capitulo 8. Incorpore animagdes, sons, pausae vidas exteas a0 jogo var tamanhoTotal = 300; var tamanho - carregadas / totalImagens + tamanhoTotal; context. fillStyle = 'yellow'; context. fil1Rect (100, 250, tamanho, 50); // contima ... Por ultimo, restauramos as configura¢ées anteriores do contexto (método restore) e, em ver de iniciar o jogo imediatamente, vamos mostrar um link “Jogar’, a ser criado logo adiante, para o jogador clicar quando estiver pronto: function carregando() { “ context. restore(); if (carregadas totalImagens) { iniciarObjetos(); mostrarLinkJogar() ; Como 0 jogo nao vai iniciar automaticamente, tire 0 comando que inicia aanimagio da fungio configuracoesIniciais: // Remova esta linha animacao.ligar(); E também a chamada ao método play da funcgdo carregarMusicas: // Remova esta linha musicadcao. play(); Crie entio um link para iniciar 0 jogo. Logo apés a tag , insira esse link, com uma chamada para a fungio inictarJogo: Jogar 183 87 Telade loading Casa do Cédigo ‘Vamos configurar sua aparéncia e posi¢ao via CSS. Vocé pode configurar esse botio da forma que quiser, ¢ nao ¢ 0 foco deste livro ficar detalhando formatagSes em CSS. Caso nao tenha muita pratica, ai esta uma formatagao sugerida, No pacote de downloads, existe a imagem botao—Jjogar.png, que usei como fundo. Na seco do documento (pode ser apés os scripts), crie uma tag 184

Você também pode gostar