# Construindo um Jogo Espacial Parte 3: Adicionando Movimento Pense nos seus jogos favoritos – o que os torna cativantes não são apenas os gráficos bonitos, mas a forma como tudo se move e responde às suas ações. No momento, seu jogo espacial é como uma pintura bonita, mas estamos prestes a adicionar movimento que o trará à vida. Quando os engenheiros da NASA programaram o computador de orientação para as missões Apollo, enfrentaram um desafio semelhante: como fazer uma espaçonave responder aos comandos do piloto enquanto mantém correções automáticas de curso? Os princípios que aprenderemos hoje ecoam esses mesmos conceitos – gerenciar o movimento controlado pelo jogador junto com comportamentos automáticos do sistema. Nesta lição, você aprenderá como fazer as naves espaciais deslizarem pela tela, responderem aos comandos do jogador e criarem padrões de movimento suaves. Vamos dividir tudo em conceitos gerenciáveis que se complementam naturalmente. Ao final, os jogadores poderão pilotar sua nave heroica pela tela enquanto as naves inimigas patrulham acima. Mais importante, você entenderá os princípios fundamentais que alimentam os sistemas de movimento dos jogos. ## Quiz Pré-Aula [Quiz pré-aula](https://ff-quizzes.netlify.app/web/quiz/33) ## Entendendo o Movimento em Jogos Os jogos ganham vida quando as coisas começam a se mover, e existem basicamente duas maneiras de isso acontecer: - **Movimento controlado pelo jogador**: Quando você pressiona uma tecla ou clica com o mouse, algo se move. Essa é a conexão direta entre você e o mundo do jogo. - **Movimento automático**: Quando o próprio jogo decide mover as coisas – como aquelas naves inimigas que precisam patrulhar a tela, independentemente do que você esteja fazendo. Fazer objetos se moverem na tela de um computador é mais simples do que você imagina. Lembra-se das coordenadas x e y da aula de matemática? É exatamente isso que estamos usando aqui. Quando Galileu rastreou as luas de Júpiter em 1610, ele estava essencialmente fazendo a mesma coisa – traçando posições ao longo do tempo para entender os padrões de movimento. Mover coisas na tela é como criar uma animação de flipbook – você precisa seguir esses três passos simples: 1. **Atualizar a posição** – Alterar onde seu objeto deve estar (talvez movê-lo 5 pixels para a direita) 2. **Apagar o quadro antigo** – Limpar a tela para que você não veja rastros fantasmagóricos por toda parte 3. **Desenhar o novo quadro** – Colocar seu objeto em sua nova posição Faça isso rápido o suficiente e pronto! Você terá um movimento suave que parece natural para os jogadores. Aqui está como isso pode parecer em código: ```javascript // Set the hero's location hero.x += 5; // Clear the rectangle that hosts the hero ctx.clearRect(0, 0, canvas.width, canvas.height); // Redraw the game background and hero ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = "black"; ctx.drawImage(heroImg, hero.x, hero.y); ``` **O que este código faz:** - **Atualiza** a coordenada x do herói em 5 pixels para movê-lo horizontalmente - **Limpa** toda a área do canvas para remover o quadro anterior - **Preenche** o canvas com uma cor de fundo preta - **Redesenha** a imagem do herói em sua nova posição ✅ Você consegue pensar em um motivo pelo qual redesenhar seu herói várias vezes por segundo pode gerar custos de desempenho? Leia sobre [alternativas para este padrão](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas). ## Lidando com eventos de teclado É aqui que conectamos a entrada do jogador à ação do jogo. Quando alguém aperta a barra de espaço para disparar um laser ou pressiona uma tecla de seta para desviar de um asteroide, seu jogo precisa detectar e responder a essa entrada. Os eventos de teclado acontecem no nível da janela, o que significa que toda a janela do navegador está ouvindo essas teclas pressionadas. Cliques do mouse, por outro lado, podem ser vinculados a elementos específicos (como clicar em um botão). Para nosso jogo espacial, focaremos nos controles de teclado, já que é isso que dá aos jogadores aquela sensação clássica de arcade. Isso me lembra como os operadores de telégrafo no século XIX tinham que traduzir entradas de código Morse em mensagens significativas – estamos fazendo algo semelhante, traduzindo teclas pressionadas em comandos de jogo. Para lidar com um evento, você precisa usar o método `addEventListener()` da janela e fornecer dois parâmetros de entrada. O primeiro parâmetro é o nome do evento, por exemplo, `keyup`. O segundo parâmetro é a função que deve ser invocada como resultado do evento ocorrido. Aqui está um exemplo: ```javascript window.addEventListener('keyup', (evt) => { // evt.key = string representation of the key if (evt.key === 'ArrowUp') { // do something } }); ``` **Desmembrando o que acontece aqui:** - **Escuta** eventos de teclado em toda a janela - **Captura** o objeto do evento que contém informações sobre qual tecla foi pressionada - **Verifica** se a tecla pressionada corresponde a uma tecla específica (neste caso, a seta para cima) - **Executa** o código quando a condição é atendida Para eventos de tecla, há duas propriedades no evento que você pode usar para ver qual tecla foi pressionada: - `key` - esta é uma representação em string da tecla pressionada, por exemplo, `'ArrowUp'` - `keyCode` - esta é uma representação numérica, por exemplo, `37`, que corresponde a `ArrowLeft` ✅ Manipulação de eventos de tecla é útil fora do desenvolvimento de jogos. Quais outros usos você consegue pensar para essa técnica? ### Teclas especiais: atenção! Algumas teclas têm comportamentos embutidos no navegador que podem interferir no seu jogo. As teclas de seta rolam a página e a barra de espaço faz a página descer – comportamentos que você não quer quando alguém está tentando pilotar sua nave espacial. Podemos evitar esses comportamentos padrão e deixar nosso jogo lidar com a entrada. Isso é semelhante a como os primeiros programadores de computadores tinham que substituir interrupções do sistema para criar comportamentos personalizados – estamos apenas fazendo isso no nível do navegador. Veja como: ```javascript const onKeyDown = function (e) { console.log(e.keyCode); switch (e.keyCode) { case 37: case 39: case 38: case 40: // Arrow keys case 32: e.preventDefault(); break; // Space default: break; // do not block other keys } }; window.addEventListener('keydown', onKeyDown); ``` **Entendendo este código de prevenção:** - **Verifica** códigos de tecla específicos que podem causar comportamentos indesejados no navegador - **Previne** a ação padrão do navegador para as teclas de seta e barra de espaço - **Permite** que outras teclas funcionem normalmente - **Usa** `e.preventDefault()` para interromper o comportamento embutido do navegador ## Movimento induzido pelo jogo Agora vamos falar sobre objetos que se movem sem a entrada do jogador. Pense em naves inimigas cruzando a tela, balas voando em linha reta ou nuvens flutuando ao fundo. Esse movimento autônomo faz com que o mundo do jogo pareça vivo, mesmo quando ninguém está tocando nos controles. Usamos temporizadores embutidos do JavaScript para atualizar posições em intervalos regulares. Esse conceito é semelhante ao funcionamento de relógios de pêndulo – um mecanismo regular que aciona ações consistentes e cronometradas. Veja como isso pode ser simples: ```javascript const id = setInterval(() => { // Move the enemy on the y axis enemy.y += 10; }, 100); ``` **O que este código de movimento faz:** - **Cria** um temporizador que é executado a cada 100 milissegundos - **Atualiza** a coordenada y do inimigo em 10 pixels a cada vez - **Armazena** o ID do intervalo para que possamos pará-lo mais tarde, se necessário - **Move** o inimigo para baixo na tela automaticamente ## O loop do jogo Aqui está o conceito que une tudo – o loop do jogo. Se o seu jogo fosse um filme, o loop do jogo seria o projetor de filmes, exibindo quadro após quadro tão rápido que tudo parece se mover suavemente. Todo jogo tem um desses loops rodando nos bastidores. É uma função que atualiza todos os objetos do jogo, redesenha a tela e repete esse processo continuamente. Isso mantém o controle do seu herói, todos os inimigos, quaisquer lasers voando por aí – todo o estado do jogo. Esse conceito me lembra como os primeiros animadores de filmes, como Walt Disney, tinham que redesenhar os personagens quadro a quadro para criar a ilusão de movimento. Estamos fazendo a mesma coisa, só que com código em vez de lápis. Aqui está como um loop de jogo geralmente se parece, expresso em código: ```javascript const gameLoopId = setInterval(() => { function gameLoop() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = "black"; ctx.fillRect(0, 0, canvas.width, canvas.height); drawHero(); drawEnemies(); drawStaticObjects(); } gameLoop(); }, 200); ``` **Entendendo a estrutura do loop do jogo:** - **Limpa** todo o canvas para remover o quadro anterior - **Preenche** o fundo com uma cor sólida - **Desenha** todos os objetos do jogo em suas posições atuais - **Repete** esse processo a cada 200 milissegundos para criar uma animação suave - **Gerencia** a taxa de quadros controlando o tempo do intervalo ## Continuando o Jogo Espacial Agora vamos adicionar movimento à cena estática que você construiu anteriormente. Vamos transformá-la de uma captura de tela em uma experiência interativa. Vamos trabalhar nisso passo a passo para garantir que cada peça se construa sobre a anterior. Pegue o código de onde paramos na lição anterior (ou comece com o código na pasta [Parte II - inicial](../../../../6-space-game/3-moving-elements-around/your-work) se precisar começar do zero). **Aqui está o que estamos construindo hoje:** - **Controles do herói**: As teclas de seta irão pilotar sua nave espacial pela tela - **Movimento dos inimigos**: As naves alienígenas começarão seu avanço Vamos começar a implementar esses recursos. ## Passos recomendados Localize os arquivos que foram criados para você na subpasta `your-work`. Ela deve conter o seguinte: ```bash -| assets -| enemyShip.png -| player.png -| index.html -| app.js -| package.json ``` Você inicia seu projeto na pasta `your-work` digitando: ```bash cd your-work npm start ``` **O que este comando faz:** - **Navega** até o diretório do seu projeto - **Inicia** um servidor HTTP no endereço `http://localhost:5000` - **Serve** os arquivos do seu jogo para que você possa testá-los em um navegador O comando acima iniciará um servidor HTTP no endereço `http://localhost:5000`. Abra um navegador e insira esse endereço, agora ele deve renderizar o herói e todos os inimigos; nada está se movendo – ainda! ### Adicionar código 1. **Adicione objetos dedicados** para `hero`, `enemy` e `game object`, eles devem ter propriedades `x` e `y`. (Lembre-se da parte sobre [Herança ou composição](../README.md)). *DICA* `game object` deve ser aquele com `x` e `y` e a capacidade de se desenhar em um canvas. > **Dica**: Comece adicionando uma nova classe `GameObject` com seu construtor delineado como abaixo, e depois desenhe-a no canvas: ```javascript class GameObject { constructor(x, y) { this.x = x; this.y = y; this.dead = false; this.type = ""; this.width = 0; this.height = 0; this.img = undefined; } draw(ctx) { ctx.drawImage(this.img, this.x, this.y, this.width, this.height); } } ``` **Entendendo esta classe base:** - **Define** propriedades comuns que todos os objetos do jogo compartilham (posição, tamanho, imagem) - **Inclui** uma bandeira `dead` para rastrear se o objeto deve ser removido - **Fornece** um método `draw()` que renderiza o objeto no canvas - **Define** valores padrão para todas as propriedades que as classes filhas podem substituir Agora, estenda este `GameObject` para criar o `Hero` e o `Enemy`: ```javascript class Hero extends GameObject { constructor(x, y) { super(x, y); this.width = 98; this.height = 75; this.type = "Hero"; this.speed = 5; } } ``` ```javascript class Enemy extends GameObject { constructor(x, y) { super(x, y); this.width = 98; this.height = 50; this.type = "Enemy"; const id = setInterval(() => { if (this.y < canvas.height - this.height) { this.y += 5; } else { console.log('Stopped at', this.y); clearInterval(id); } }, 300); } } ``` **Conceitos-chave nestas classes:** - **Herdam** de `GameObject` usando a palavra-chave `extends` - **Chamam** o construtor pai com `super(x, y)` - **Definem** dimensões e propriedades específicas para cada tipo de objeto - **Implementam** movimento automático para inimigos usando `setInterval()` 2. **Adicione manipuladores de eventos de tecla** para lidar com a navegação por teclas (mover o herói para cima/baixo/esquerda/direita) *LEMBRE-SE* é um sistema cartesiano, o canto superior esquerdo é `0,0`. Também lembre-se de adicionar código para interromper o *comportamento padrão* > **Dica**: Crie sua função `onKeyDown` e anexe-a à janela: ```javascript const onKeyDown = function (e) { console.log(e.keyCode); // Add the code from the lesson above to stop default behavior switch (e.keyCode) { case 37: case 39: case 38: case 40: // Arrow keys case 32: e.preventDefault(); break; // Space default: break; // do not block other keys } }; window.addEventListener("keydown", onKeyDown); ``` **O que este manipulador de eventos faz:** - **Escuta** eventos de tecla pressionada em toda a janela - **Registra** o código da tecla para ajudar você a depurar quais teclas estão sendo pressionadas - **Previne** o comportamento padrão do navegador para teclas de seta e barra de espaço - **Permite** que outras teclas funcionem normalmente Verifique o console do seu navegador neste ponto e observe as teclas sendo registradas. 3. **Implemente** o [Padrão Pub-Sub](../README.md), isso manterá seu código limpo enquanto você segue as próximas partes. O padrão Publish-Subscribe ajuda a organizar seu código separando a detecção de eventos do tratamento de eventos. Isso torna seu código mais modular e fácil de manter. Para fazer esta última parte, você pode: 1. **Adicionar um listener de eventos** na janela: ```javascript window.addEventListener("keyup", (evt) => { if (evt.key === "ArrowUp") { eventEmitter.emit(Messages.KEY_EVENT_UP); } else if (evt.key === "ArrowDown") { eventEmitter.emit(Messages.KEY_EVENT_DOWN); } else if (evt.key === "ArrowLeft") { eventEmitter.emit(Messages.KEY_EVENT_LEFT); } else if (evt.key === "ArrowRight") { eventEmitter.emit(Messages.KEY_EVENT_RIGHT); } }); ``` **O que este sistema de eventos faz:** - **Detecta** entrada de teclado e a converte em eventos personalizados do jogo - **Separa** a detecção de entrada da lógica do jogo - **Facilita** a alteração dos controles mais tarde sem afetar o código do jogo - **Permite** que vários sistemas respondam à mesma entrada 2. **Crie uma classe EventEmitter** para publicar e assinar mensagens: ```javascript class EventEmitter { constructor() { this.listeners = {}; } on(message, listener) { if (!this.listeners[message]) { this.listeners[message] = []; } this.listeners[message].push(listener); } 3. **Adicione constantes** e configure o EventEmitter: ```javascript const Messages = { KEY_EVENT_UP: "KEY_EVENT_UP", KEY_EVENT_DOWN: "KEY_EVENT_DOWN", KEY_EVENT_LEFT: "KEY_EVENT_LEFT", KEY_EVENT_RIGHT: "KEY_EVENT_RIGHT", }; let heroImg, enemyImg, laserImg, canvas, ctx, gameObjects = [], hero, eventEmitter = new EventEmitter(); ``` **Entendendo a configuração:** - **Define** constantes de mensagem para evitar erros de digitação e facilitar refatorações - **Declara** variáveis para imagens, contexto do canvas e estado do jogo - **Cria** um emissor de eventos global para o sistema pub-sub - **Inicializa** um array para armazenar todos os objetos do jogo 4. **Inicialize o jogo** ```javascript function initGame() { gameObjects = []; createEnemies(); createHero(); eventEmitter.on(Messages.KEY_EVENT_UP, () => { hero.y -= 5; }); eventEmitter.on(Messages.KEY_EVENT_DOWN, () => { hero.y += 5; }); eventEmitter.on(Messages.KEY_EVENT_LEFT, () => { hero.x -= 5; }); 4. **Configure o loop do jogo** Refatore a função `window.onload` para inicializar o jogo e configurar um loop de jogo em um bom intervalo. Você também adicionará um feixe de laser: ```javascript window.onload = async () => { canvas = document.getElementById("canvas"); ctx = canvas.getContext("2d"); heroImg = await loadTexture("assets/player.png"); enemyImg = await loadTexture("assets/enemyShip.png"); laserImg = await loadTexture("assets/laserRed.png"); initGame(); const gameLoopId = setInterval(() => { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = "black"; ctx.fillRect(0, 0, canvas.width, canvas.height); drawGameObjects(ctx); }, 100); }; ``` **Entendendo a configuração do jogo:** - **Aguarda** o carregamento completo da página antes de começar - **Obtém** o elemento canvas e seu contexto de renderização 2D - **Carrega** todos os recursos de imagem de forma assíncrona usando `await` - **Inicia** o loop do jogo rodando em intervalos de 100ms (10 FPS) - **Limpa** e redesenha toda a tela a cada quadro 5. **Adicione código** para mover os inimigos em um determinado intervalo Refatore a função `createEnemies()` para criar os inimigos e empurrá-los para a nova classe gameObjects: ```javascript function createEnemies() { const MONSTER_TOTAL = 5; const MONSTER_WIDTH = MONSTER_TOTAL * 98; const START_X = (canvas.width - MONSTER_WIDTH) / 2; const STOP_X = START_X + MONSTER_WIDTH; for (let x = START_X; x < STOP_X; x += 98) { for (let y = 0; y < 50 * 5; y += 50) { const enemy = new Enemy(x, y); enemy.img = enemyImg; gameObjects.push(enemy); } } } ``` **O que a criação de inimigos faz:** - **Calcula** posições para centralizar os inimigos na tela - **Cria** uma grade de inimigos usando loops aninhados - **Atribui** a imagem do inimigo a cada objeto inimigo - **Adiciona** cada inimigo ao array global de objetos do jogo e adicione uma função `createHero()` para realizar um processo semelhante para o herói. ```javascript function createHero() { hero = new Hero( canvas.width / 2 - 45, canvas.height - canvas.height / 4 ); hero.img = heroImg; gameObjects.push(hero); } ``` **O que a criação do herói faz:** - **Posiciona** o herói no centro inferior da tela - **Atribui** a imagem do herói ao objeto herói - **Adiciona** o herói ao array de objetos do jogo para renderização e, por fim, adicione uma função `drawGameObjects()` para iniciar a renderização: ```javascript function drawGameObjects(ctx) { gameObjects.forEach(go => go.draw(ctx)); } ``` **Entendendo a função de renderização:** - **Itera** por todos os objetos do jogo no array - **Chama** o método `draw()` em cada objeto - **Passa** o contexto do canvas para que os objetos possam se renderizar Seus inimigos devem começar a avançar contra sua nave espacial! } } ``` and add a `createHero()` function to do a similar process for the hero. ```javascript function createHero() { hero = new Hero( canvas.width / 2 - 45, canvas.height - canvas.height / 4 ); hero.img = heroImg; gameObjects.push(hero); } ``` e, por fim, adicione uma função `drawGameObjects()` para iniciar a renderização: ```javascript function drawGameObjects(ctx) { gameObjects.forEach(go => go.draw(ctx)); } ``` Seus inimigos devem começar a avançar contra sua nave espacial! --- ## Desafio do Agente GitHub Copilot 🚀 Aqui está um desafio que vai melhorar o acabamento do seu jogo: adicionar limites e controles suaves. Atualmente, seu herói pode sair da tela, e o movimento pode parecer travado. **Sua missão:** Faça sua nave espacial parecer mais realista implementando limites na tela e movimentos fluidos. Isso é semelhante ao modo como os sistemas de controle de voo da NASA evitam que espaçonaves excedam parâmetros operacionais seguros. **O que você deve construir:** Crie um sistema que mantenha sua nave espacial herói na tela e faça os controles parecerem suaves. Quando os jogadores mantêm pressionada uma tecla de seta, a nave deve deslizar continuamente em vez de se mover em passos discretos. Considere adicionar um feedback visual quando a nave atingir os limites da tela – talvez um efeito sutil para indicar o limite da área de jogo. Saiba mais sobre [modo agente](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) aqui. ## 🚀 Desafio A organização do código se torna cada vez mais importante à medida que os projetos crescem. Você pode ter notado que seu arquivo está ficando cheio de funções, variáveis e classes misturadas. Isso me lembra como os engenheiros que organizaram o código da missão Apollo tiveram que criar sistemas claros e fáceis de manter para que várias equipes pudessem trabalhar simultaneamente. **Sua missão:** Pense como um arquiteto de software. Como você organizaria seu código para que, daqui a seis meses, você (ou um colega de equipe) pudesse entender o que está acontecendo? Mesmo que tudo permaneça em um único arquivo por enquanto, você pode criar uma organização melhor: - **Agrupando funções relacionadas** com cabeçalhos de comentários claros - **Separando responsabilidades** - mantenha a lógica do jogo separada da renderização - **Usando convenções de nomenclatura consistentes** para variáveis e funções - **Criando módulos** ou namespaces para organizar diferentes aspectos do seu jogo - **Adicionando documentação** que explique o propósito de cada seção principal **Perguntas para reflexão:** - Quais partes do seu código são mais difíceis de entender quando você volta a elas? - Como você poderia organizar seu código para torná-lo mais fácil para outra pessoa contribuir? - O que aconteceria se você quisesse adicionar novos recursos, como power-ups ou diferentes tipos de inimigos? ## Quiz Pós-Aula [Quiz pós-aula](https://ff-quizzes.netlify.app/web/quiz/34) ## Revisão & Autoestudo Estamos construindo tudo do zero, o que é fantástico para aprender, mas aqui vai um pequeno segredo – existem alguns frameworks incríveis de JavaScript que podem lidar com grande parte do trabalho pesado para você. Assim que você se sentir confortável com os fundamentos que abordamos, vale a pena [explorar o que está disponível](https://github.com/collections/javascript-game-engines). Pense nos frameworks como ter uma caixa de ferramentas bem equipada, em vez de fazer cada ferramenta à mão. Eles podem resolver muitos desses desafios de organização de código que mencionamos, além de oferecer recursos que levariam semanas para você desenvolver sozinho. **Coisas que valem a pena explorar:** - Como os motores de jogos organizam o código – você ficará impressionado com os padrões inteligentes que eles usam - Truques de desempenho para fazer jogos em canvas rodarem de forma super suave - Recursos modernos do JavaScript que podem tornar seu código mais limpo e fácil de manter - Diferentes abordagens para gerenciar objetos do jogo e suas relações ## Tarefa [Comente seu código](assignment.md) --- **Aviso Legal**: Este documento foi traduzido usando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, esteja ciente de que traduções automatizadas podem conter erros ou imprecisões. O documento original em seu idioma nativo deve ser considerado a fonte autoritativa. Para informações críticas, recomenda-se a tradução profissional humana. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes do uso desta tradução.