Compartilhar via


Desenvolvimento de jogos

Um jogo da Web em uma hora

Michael Oneppo

Baixar o código de exemplo

Não é necessário um conjunto de habilidades totalmente novo para desenvolver jogos. Na verdade, suas habilidades de desenvolvimento da Web atuais em HTML, JavaScript, CSS e assim por diante funcionam bem para uma ampla variedade de jogos. Quando você cria um jogo com tecnologias da Web, ele será executado em praticamente qualquer dispositivo com um navegador.

Para provar isso, vou demonstrar a criação de um jogo do zero usando tecnologias da Web e apenas duas bibliotecas externas. Farei em menos de uma hora. Falarei sobre uma variedade de tópicos de desenvolvimento de jogos, desde o design básico e layout, controles e sprites, até inteligência artificial (AI) para um adversário simples. Irei até mesmo desenvolver o jogo para que funcione em computadores, tablets e smartphones. Se você tiver alguma experiência com programação como um desenvolvedor da Web ou de outro domínio de desenvolvimento, mas nenhuma experiência em criar jogos, neste artigo, você poderá começar. Se você me der uma hora, eu prometo mostrar o caminho.

Colocar em funcionamento

Vou fazer todo o desenvolvimento no Visual Studio, o que permitirá a execução rápida do aplicativo da Web conforme faço alterações. Certifique-se de ter a versão mais recente do Visual Studio (baixada em bit.ly/1xEjEnX) para que você possa acompanhá-lo. Eu usei o Visual Studio 2013 Pro, mas atualizei o código com o Visual Studio 2013 Community.

Esse aplicativo não exigirá nenhum código de servidor, portanto, eu começo criando um projeto de página da Web nova e vazia no Visual Studio. Usarei o modelo do C# vazio para um site selecionando a opção Visual C# e selecionando Arquivo | Novo | Site ASP.NET Vazio.

O arquivo HTML de índice requer apenas três recursos: jQuery, uma folha de estilos principal e um arquivo JavaScript principal. Eu posso adicionar um arquivo CSS vazio para o projeto chamado style.css e um arquivo JavaScript vazio chamado ping.js para evitar erros ao carregar a página:

<!DOCTYPE html>
<html>
<head>
  <script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.1.1.min.js"></script>
  <script src="ping.js"></script>
  <link rel="stylesheet" href="style.css"></script>
</head>
<body>
</body>
</html>

Design básico

O jogo que estou criando é uma variante do Pong que eu chamo de Ping. Ping tem essencialmente as mesmas regras que Pong, exceto que o jogador segura a bola quando ela vem em sua direção e dispara a bola de volta diretamente ou em um ângulo para cima ou para baixo. É geralmente melhor desenhar como você gostaria que o jogo se pareça antes de sua criação. Para esse jogo, o layout geral que desejo ver é mostrado na Figura 1.

O design geral de Ping
Figura 1 O design geral de Ping

Depois de ter desenvolvido o layout de projeto do jogo, é necessário apenas adicionar cada elemento HTML para criar o jogo. Observe, porém, é que eu agruparei o placar e controles para garantir que eles fiquem juntos. Então, individualmente, você pode ver os elementos adicionados, como mostrado na Figura 2.

Figura 2 O layout HTML inicial

<div id="arena">
  <div id="score">
    <h1>
      <span id="playerScore">0</span>
      -
      <span id="opponentScore">0</span>
    </h1>
  </div>
  <div id="player"></div>
  <div id="opponent"></div>
  <div id="ball"></div>
  <div id="controls-left">
    <div id="up"></div>
    <div id="down"></div>
  </div>
  <div id="controls-right">
    <div id="left"></div>
    <div id="right"></div>
  </div>
</div>

Brincar com estilo

Se você carregar esta página, não veriam nada porque não há nenhum estilo aplicado. Já configurei um link para um arquivo main.css em meu HTML, portanto, vou colocar todos os CSS em um novo arquivo com esse nome. A primeira coisa a fazer é posicionar tudo na tela. O corpo da página deve ocupar a tela inteira, portanto, eu irei configurar isso primeiro:

body {
  margin: 0px;
  height: 100%;
}

Em segundo lugar, preciso ter a arena em toda tela com a imagem de plano de fundo da arena (veja a Figura 3) aplicada:

#arena {
  background-image: url(arena.png);
  background-size: 100% 100%;
  margin: 0px;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

A imagem de plano de fundo da arena
Figura 3 A imagem de plano de fundo da arena

Em seguida, irei posicionar o placar. Eu quero que apareça na parte superior central, acima dos outros elementos. A posição de comando: absoluto me permite colocá-lo onde desejar e à esquerda: 50% coloca no meio entre a parte superior da janela, mas começando no lado mais à esquerda do elemento do placar. Para garantir que esteja centralizado perfeitamente, eu uso a propriedade transform e a propriedade z-index garante que esteja sempre na parte superior:

#score {
  position: absolute;
  z-index: 1000;
  left: 50%;
  top: 5%;
  transform: translate(-50%, 0%);
}

Também quero que a fonte do texto seja em tema antigo. Os navegadores mais modernos permitem incluir minhas próprias fontes. Descobri a fonte Press Start 2p adequada em codeman38 (zone38.net). Para adicionar a fonte ao placar, preciso criar um novo tipo de fonte:

@font-face {
  font-family: 'PressStart2P';
  src: url('PressStart2P.woff');
}

Agora, as pontuações estão em uma marca h1, portanto, posso definir a fonte para todas as marcas h1. Caso a fonte esteja ausente, fornecerei algumas opções de backup:

h1 {
  font-family: 'PressStart2P', 'Georgia', serif;
}

Para os outros elementos, usarei uma folha sprite de imagens. Uma folha sprite contém todas as imagens necessárias para o jogo em um arquivo (consulte a Figura 4).

A folha sprite para Ping
Figura 4 A folha sprite para Ping

Qualquer elemento que tenha uma imagem nesta folha terá uma classe sprite atribuída. Em seguida, para cada elemento, usarei a posição de plano de fundo para definir qual parte da planilha sprite desejo mostrar:

.sprite {
  background-image: url("sprites.png");
  width: 128px;
  height: 128px;
}

Em seguida, vou adicionar a classe sprite a todos os elementos que usarão a folha sprite. Terei que voltar rapidamente para o HTML para fazer isso:

<div id="player" class="sprite"></div>
<div id="opponent" class="sprite"></div>
<div id="ball" class="sprite"></div>
<div id="controls-left">
  <div id="up" class="sprite"></div>
  <div id="down" class="sprite"></div>
</div>
<div id="controls-right">
  <div id="left" class="sprite"></div>
  <div id="right" class="sprite"></div>
</div>

Agora preciso indicar as posições de cada sprite na folha de cada elemento. Mais uma vez, farei isso usando a posição de plano de fundo, conforme mostrado na Figura 5.

Figura 5 Adicionando deslocamentos para a folha sprite

#player {
  position: absolute;
  background-position: 0px 128px;
}
#opponent {
  position: absolute;
  background-position: 0px 0px;
}
#ball {
  position: absolute;
  background-position: 128px 128px;
}
#right {
  background-position: 64px 192px;
}
#left {
  background-position: 64px 0px;
}
#down {
  background-position: 128px 192px;
}
#up {
  background-position: 128px 0px;
}

A posição: a propriedade absolute no jogador, oponente e bola permitirá eu movê-los usando JavaScript. Se você olhar para a página agora, verá que os controles e a bola têm partes desnecessárias anexadas a elas. Isso ocorre porque os tamanhos do sprite são menores do que os pixels de 128 padrão, portanto, vou ajustá-los para o tamanho correto. Como há apenas uma bola, eu vou definir seu tamanho diretamente:

#ball {
  position: absolute;
  width: 64px;
  height: 64px;
  background-position: 128px 128px;
}

Há quatro elementos de controle (botões que o usuário pode pressionar para mover o jogador), portanto, cabe a mim criar uma classe especial para eles. Também vou adicionar uma margem para que eles tenham um pouco de espaço em torno:

.control {
  margin: 16px;
  width: 64px;
  height: 64px;
}

Depois de adicionar essa classe, o jogo tem controles com uma aparência muito melhor:

<div id="controls-left">
  <div id="up" class="sprite control"></div>
  <div id="down" class="sprite control"></div>
</div>
<div id="controls-right">
  <div id="left" class="sprite control"></div>
  <div id="right" class="sprite control"></div>
</div>

A última coisa que preciso fazer é posicionar os controles para que eles fiquem próximos aos polegares do usuário quando a página estiver em execução em um dispositivo móvel. Eu fixarei eles nos cantos inferiores:

#controls-left {
  position: absolute;
  left: 0; bottom: 0;
}
#controls-right {
  position: absolute;
  right: 0; bottom: 0;
}

Uma coisa boa sobre esse design é que tudo é definido com posições relativas. Isso significa que a tela pode ser um número de tamanhos diferentes e ainda deixa o jogo com boa aparência.

Siga a bola saltitante

Agora vou fazer a bola se mover. Para o código JavaScript, indiquei um arquivo chamado ping.js no HTML, assim como fiz com o CSS. Vou adicionar esse código para um novo arquivo com esse nome. Vou criar objetos para a bola e cada um dos jogadores, mas usarei o padrão de fábrica para os objetos.

Este é um conceito simples. A função Ball cria uma nova bola quando você a chamar. Não é necessário usar a nova palavra-chave. Esse padrão diminui alguma da confusão em torno dessa variável ao esclarecer as propriedades de objetos disponíveis. E como eu tenho apenas uma hora para criar esse jogo, preciso minimizar qualquer conceito confuso.

A estrutura desse padrão, conforme eu crio a classe Ball simples, é mostrada na Figura 6.

Figura 6 A classe Ball

var Ball = function( {
  // List of variables only the object can see (private variables).
  var velocity = [0,0];
  var position = [0,0];
  var element = $('#ball');
  var paused = false;
  // Method that moves the ball based on its velocity. This method is only used
  // internally and will not be made accessible outside of the object.
  function move(t) {
  }
  // Update the state of the ball, which for now just checks  
  // if the play is paused and moves the ball if it is not.  
  // This function will be provided as a method on the object.
  function update(t) {
    // First the motion of the ball is handled
    if(!paused) {
      move(t);
    }
  }
  // Pause the ball motion.
  function pause() {
    paused = true;
  }
  // Start the ball motion.
  function start() {
    paused = false;
  }
  // Now explicitly set what consumers of the Ball object can use.
  // Right now this will just be the ability to update the state of the ball,
  // and start and stop the motion of the ball.
  return {
    update:       update,
    pause:        pause,
    start:        start
}

Para criar uma nova bola, eu simplesmente chamo essa função definida:

var ball = Ball();

Agora quero fazer com que a bola se mova e pule na tela. Primeiro, preciso chamar a função update em um intervalo para criar uma animação da bola. Os navegadores modernos fornecem uma função destinada a esse propósito chamada requestAnimationFrame. Isso usa uma função como um argumento e chamará essa função passed-in na próxima vez que ele executar seu ciclo de animação. Isso permite que a bola se mova nas etapas suaves quando o navegador estiver pronto para uma atualização. Quando ela chama a função passed-in, atribuirá a ela o tempo em segundos, desde que a página foi carregada. Isso é fundamental para garantir que as animações sejam consistentes ao longo do tempo. No jogo, o uso de requestAnimationFrame aparece da seguinte maneira:

var lastUpdate = 0;
var ball = Ball();
function update(time) {
  var t = time - lastUpdate;
  lastUpdate = time;
  ball.update(t);
  requestAnimationFrame(update);
}
requestAnimationFrame(update);

Observe que requestAnimationFrame é chamado novamente na função, conforme a bola conclui a atualização. Isso garante a animação contínua.

Embora esse código funcione, pode haver um problema onde o script começa a ser executado antes da página estar totalmente carregada. Para evitar isso, eu iniciarei o código quando a página estiver carregada, usando o jQuery:

var ball;
var lastUpdate;
$(document).ready(function() {
  lastUpdate = 0;
  ball = Ball();
  requestAnimationFrame(update);
});

Como sei que a velocidade da bola (velocidade) e o tempo decorrido desde sua última atualização, posso fazer uma física simples para mover a bola para frente:

var position = [300, 300];
var velocity = [-1, -1];
var move = function(t) {
  position[0] += velocity[0] * t;
  position[1] += velocity[1] * t;  
  element.css('left', position[0] + 'px');
  element.css('top', position[1] + 'px');
}

Tente executar o código e você verá a bola se mover em um ângulo e fora da tela. Isso é divertido por um segundo, mas depois que a bola fica fora da borda da tela, interrompe a diversão. Portanto, a próxima etapa é fazer com que a bola salte nas bordas da tela, conforme implementado na Figura 7. Adicionar esse código e executar o aplicativo mostrará uma bola saltitante continuamente.

Figura 7 Física de pulo da bola simples

var move = function(t) {
  // If the ball hit the top or bottom, reverse the vertical speed.
  if (position[1] <= 0 || position[1] >= innerHeight) {
   velocity[1] = -velocity[1];
  }
  // If the ball hit the left or right sides, reverse the horizontal speed.
  if (position[0] <= 0 || position[0] >= innerWidth) {
   velocity[0] = -velocity[0];
  }
  position[0] += velocity[0] * t;
  position[1] += velocity[1] * t; 
  element.css('left', (position[0] - 32) + 'px');
  element.css('top', (position[1] - 32) + 'px');
}

Um jogador móvel

Agora é hora de criar os objetos Player. A primeira etapa na elaboração da classe player será criar a função move alterar a posição do jogador. A variável lateral indicará qual lado do campo o jogador estará, que determinará como posicionar o jogador horizontalmente. O valor de y, passado para a função move, será quanto o jogador se moverá para cima ou para baixo:

var Player = function (elementName, side) {
  var position = [0,0];
  var element = $('#'+elementName);
  var move = function(y) {
  }
  return {
    move: move,
    getSide:      function()  { return side; },
    getPosition:  function()  { return position; }
  }
}

A Figura 8 apresenta o movimento do jogador, interrompendo o movimento se o sprite do jogador alcançar a parte superior ou inferior da janela.

Agora posso criar dois jogadores e movê-los para a seu lado apropriado da tela:

player = Player('player', 'left');
player.move(0);
opponent = Player('opponent', 'right');
opponent.move(0);

Figura 8 Controles de movimento do sprite do jogador

var move = function(y) {
  // Adjust the player's position.
  position[1] += y;
  // If the player is off the edge of the screen, move it back.
  if (position[1] <= 0)  {
    position[1] = 0;
  }
  // The height of the player is 128 pixels, so stop it before any
  // part of the player extends off the screen.
  if (position[1] >= innerHeight - 128) {
    position[1] = innerHeight - 128;
  }
  // If the player is meant to stick to the right side, set the player position
  // to the right edge of the screen.
  if (side == 'right') {
    position[0] = innerWidth - 128;
  }
  // Finally, update the player's position on the page.
  element.css('left', position[0] + 'px');
  element.css('top', position[1] + 'px');
}

Entrada do teclado

Portanto, em teoria, você pode mover o jogador, mas ele não se moverá sem instruções. Adicione alguns controles para o jogador na esquerda. Você quer duas maneiras de controlar esse jogador: usando o teclado (em computadores) e tocando nos controles (tablets e telefones).

Para garantir a consistência entre as entradas de toque e as entradas do mouse em várias plataformas, usarei a excelente estrutura unificada Hand.js (handjs.codeplex.com). Primeiro, vou adicionar o script para HTML na seção de cabeçalho:

 

<script src="hand.minified-1.3.8.js"></script>

A Figure 9 demonstra o uso de Hand.js e jQuery para controlar o jogador quando você pressiona as teclas A e Z ou quando você toca nos controles.

Figura 9 Adicionando controles de toque e teclado

var distance = 24;  // The amount to move the player each step.
$(document).ready(function() {
  lastUpdate = 0;
  player = Player('player', 'left');
  player.move(0);
  opponent = Player('opponent', 'right');
  opponent.move(0);
  ball = Ball();
  // pointerdown is the universal event for all types of pointers -- a finger,
  // a mouse, a stylus and so on.
  $('#up')    .bind("pointerdown", function() {player.move(-distance);});
  $('#down')  .bind("pointerdown", function() {player.move(distance);});
  requestAnimationFrame(update);
});
$(document).keydown(function(event) {
  var event = event || window.event;
  // This code converts the keyCode (a number) from the event to an uppercase
  // letter to make the switch statement easier to read.
  switch(String.fromCharCode(event.keyCode).toUpperCase()) {
    case 'A':
      player.move(-distance);
      break;
    case 'Z':
      player.move(distance);
      break;
  }
  return false;
});

Segurar a bola

Como a bola quica, quero permitir que os jogadores a segurem. Quando é segurada, a bola tem um proprietário e segue o movimento do proprietário. A Figura 10 adiciona funcionalidade ao método de movimento da bola, permitindo que a bola siga um proprietário.

Figura 10 Fazer a bola seguir seu proprietário

var move = function(t) {
  // If there is an owner, move the ball to match the owner's position.
  if (owner !== undefined) {
    var ownerPosition = owner.getPosition();
    position[1] = ownerPosition[1] + 64;
    if (owner.getSide() == 'left') {
      position[0] = ownerPosition[0] + 64;
    } else {
      position[0] = ownerPosition[0];
    }
  // Otherwise, move the ball using physics. Note the horizontal bouncing
  // has been removed -- ball should pass by a player if it
  // isn't caught.
  } else {
    // If the ball hits the top or bottom, reverse the vertical speed.
    if (position[1] - 32 <= 0 || position[1] + 32 >= innerHeight) {
      velocity[1] = -velocity[1];
    }
    position[0] += velocity[0] * t;
    position[1] += velocity[1] * t;  
  }
  element.css('left', (position[0] - 32) + 'px');
  element.css('top',  (position[1] - 32) + 'px');
}

Atualmente, não há nenhuma maneira de obter a posição de um objeto Player, portanto, adicionarei os acessores getPosition e getSide ao objeto Player:

return {
  move: move,
  getSide:      function()  { return side; },
  getPosition:  function()  { return position; }
}

Agora, se a bola tiver um proprietário, ela seguirá esse proprietário. Mas como determinar o proprietário? Alguém precisa segurar a bola. A Figura 11 mostra como determinar quando um os sprites do jogador toca a bola. Quando isso acontece, definirei o proprietário da bola para esse jogador.

Figura 11 A detecção de colisão da bola e dos jogadores

var update = function(t) {
// First the motion of the ball is handled.
if(!paused) {
    move(t);
}
// The ball is under control of a player, no need to update.
if (owner !== undefined) {
    return;
}
// First, check if the ball is about to be grabbed by the player.
var playerPosition = player.getPosition();
  if (position[0] <= 128 &&
      position[1] >= playerPosition[1] &&
      position[1] <= playerPosition[1] + 128) {
    console.log("Grabbed by player!");
    owner = player;
}
// Then the opponent...
var opponentPosition = opponent.getPosition();
  if (position[0] >= innerWidth - 128 &&
      position[1] >= opponentPosition[1] &&
      position[1] <= opponentPosition[1] + 128) {
    console.log("Grabbed by opponent!");
    owner = opponent;
}

Se você tentar executar o jogo agora, você verá a bola refletida na parte superior da tela e você poderá mover o jogador para alcançá-la. Agora, como você lança a bola? É para isso que servem os controles à direita — orientar a bola. A Figura 12 adiciona uma função "fire" para o jogador, bem como uma propriedade de objetivo.

Figura 12 Orientar e disparar a bola

var aim = 0;
var fire = function() {
  // Safety check: if the ball doesn't have an owner, don't not mess with it.
  if (ball.getOwner() !== this) {
    return;
  }
  var v = [0,0];
  // Depending on the side the player is on, different directions will be thrown.
  // The ball should move at the same speed, regardless of direction --
  // with some math you can determine that moving .707 pixels on the
  // x and y directions is the same speed as moving one pixel in just one direction.
  if (side == 'left') {
    switch(aim) {
    case -1:
      v = [.707, -.707];
      break;
    case 0:
      v = [1,0];
      break;
    case 1:
      v = [.707, .707];
    }
  } else {
    switch(aim) {
    case -1:
      v = [-.707, -.707];
      break;
    case 0:
      v = [-1,0];
      break;
    case 1:
      v = [-.707, .707];
    }
  }
  ball.setVelocity(v);
  // Release control of the ball.
  ball.setOwner(undefined);
}
// The rest of the Ball definition code goes here...
return {
  move: move,
  fire: fire,
  getSide:      function()  { return side; },
  setAim:       function(a) { aim = a; },
  getPosition:  function()  { return position; },
}

A Figura 13 aumenta a função de teclado para definir o alvo do jogador e as funções de disparo. O alvo funcionará ligeiramente diferente. Quando a chave de mira for lançada, o alvo retornará de forma reta.

Figura 13 Definir a função de alvo do jogador

$(document).keydown(function(event) {
  var event = event || window.event;
  switch(String.fromCharCode(event.keyCode).toUpperCase()) {
    case 'A':
      player.move(-distance);
      break;
    case 'Z':
      player.move(distance);
      break;
    case 'K':
      player.setAim(-1);
      break;
    case 'M':
      player.setAim(1);
      break;
    case ' ':
      player.fire();
      break;
  }
  return false;
});
$(document).keyup(function(event) {
  var event = event || window.event;
  switch(String.fromCharCode(event.keyCode).toUpperCase()) {
    case 'K':
    case 'M':
      player.setAim(0);
      break;
  }
  return false;
});

A adição de final será o suporte ao toque em todos os controles. Vou fazer os controles exibidas à direita mudarem o alvo do jogador. Também farei com que tocar em qualquer lugar na tela dispara a bola:

$('#left')  .bind("pointerdown", function() {player.setAim(-1);});
$('#right') .bind("pointerdown", function() {player.setAim(1);});
$('#left')  .bind("pointerup",   function() {player.setAim(0);});
$('#right') .bind("pointerup",   function() {player.setAim(0);});
$('body')   .bind("pointerdown", function() {player.fire();});

Manter pontuação

Quando a bola passa um jogador, quero alterar a pontuação e dar a bola para esse player. Vou usar eventos personalizados para que possa separar a pontuação de qualquer um dos objetos existentes. A função de atualização está levando muito tempo, portanto, adicionarei uma nova função privada chamada checkScored:

function checkScored() {
  if (position[0] <= 0) {
    pause();
    $(document).trigger('ping:opponentScored');
  }
  if (position[0] >= innerWidth) {
    pause();
    $(document).trigger('ping:playerScored');
  }
}

A Figura 14 mostra o código que reage a esses eventos para atualizar a pontuação e passar a bola. Adicione este código na parte inferior do documento JavaScript.

Figura 14 Atualizar o placar

$(document).on('ping:playerScored', function(e) {
  console.log('player scored!');
  score[0]++;
  $('#playerScore').text(score[0]);
  ball.setOwner(opponent);
  ball.start();
});
$(document).on('ping:opponentScored', function(e) {
  console.log('opponent scored!');
  score[1]++;
  $('#opponentScore').text(score[1]);
  ball.setOwner(player);
  ball.start();
});

Agora quando a bola consegue passar seu adversário (que não é tão difícil, pois o adversário não se move) sua pontuação subirá e a bola será passada para o adversário. No entanto, o adversário apenas segurará a bola.

Ficar inteligente

Você quase tem um jogo. Se você apenas tivesse alguém com quem jogar. Como última etapa, vou mostrar como controlar o adversário com AI simples. O adversário tentará permanecer paralelo com a bola conforme ela se move. Se o adversário capturar a bola, ele se moverá aleatoriamente e disparará a bola em uma direção aleatória. Para tornar o AI um pouco mais humano, vou adicionar atrasos em tudo que é feito. Isso não é AI altamente inteligente, lembre-se, mas é alguma coisa para ter um adversário.

Ao criar esse tipo de sistema, é bom pensar nos estados. O AI adversário tem três estados possíveis: seguindo, apontando/disparando e aguardando. Serei o estado entre as seguintes ações para adicionar um elemento mais humano. Iniciar apenas com isso para o objeto AI:

function AI(playerToControl) {
  var ctl = playerToControl;
  var State = {
    WAITING: 0,
    FOLLOWING: 1,
    AIMING: 2
  }
  var currentState = State.FOLLOWING;
}

Dependendo do estado da AI, eu desejarei fazer uma ação diferente. Assim como a bola, vou criar uma função de atualização que posso chamar em requestAnimationFrame para que o AI haja de acordo com seu estado:

function update() {
  switch (currentState) {
    case State.FOLLOWING:
      // Do something to follow the ball.
      break;
    case State.WAITING:
      // Do something to wait.
      break;
    case State.AIMING:
      // Do something to aim.
      break;
  }
}

O estado FOLLOWING seguinte é simples. O adversário é movido na direção vertical da bola e o AI passa para o estado WAITING para injetar algum tempo de reação mais lento. A Figura 15 mostra esses dois estados.

Figura 15 Um AI FOLLOWING simples

function moveTowardsBall() {
  // Move the same distance the player would move, to make it fair.
  if(ball.getPosition()[1] >= ctl.getPosition()[1] + 64) {
    ctl.move(distance);
  } else {
    ctl.move(-distance);
  }
}
function update() {
  switch (currentState) {
    case State.FOLLOWING:
      moveTowardsBall();
      currentState = State.WAITING;
    case State.WAITING:
      setTimeout(function() {
        currentState = State.FOLLOWING;
      }, 400);
      break;
    }
  }
}

Com o código na Figura 15, o AI alterna entre precisar seguir a bola e esperar um segundo de divisão. Agora, adicione o código para a função de atualização de todo o jogo:

function update(time) {
  var t = time - lastUpdate;
  lastUpdate = time;
  ball.update(t);
  ai.update();
  requestAnimationFrame(update);
}

Quando você executar o jogo, verá o adversário seguindo os movimentos da bola – não um AI ruim em menos de 30 linhas de código. É claro que, se o adversário capturar a bola, ele não fará nada. Então, para o último truque da hora, é hora de lidar com as ações para o estado AIMING. Quero que o AI se mova aleatoriamente algumas vezes e, em seguida, dispare a bola em uma direção aleatória. A Figura 16 adiciona uma função privada que faz exatamente isso. Adicionar a função aimAndFire para a instrução AIMING case torna um AI totalmente funcional com o qual jogar.

Figura 16 AI para apontar e disparar

function repeat(cb, cbFinal, interval, count) {
  var timeout = function() {
    repeat(cb, cbFinal, interval, count-1);
  }
  if (count <= 0) {
    cbFinal();
  } else {
    cb();
    setTimeout(function() {
      repeat(cb, cbFinal, interval, count-1);
    }, interval);
  }
}
function aimAndFire() {
  // Repeat the motion action 5 to 10 times.
  var numRepeats = Math.floor(5 + Math.random() * 5);
  function randomMove() {
    if (Math.random() > .5) {
      ctl.move(-distance);
    } else {
      ctl.move(distance);
    }
  }
  function randomAimAndFire() {
    var d = Math.floor( Math.random() * 3 - 1 );
    opponent.setAim(d);
    opponent.fire();
    // Finally, set the state to FOLLOWING.
    currentState = State.FOLLOWING;
  }
  repeat(randomMove, randomAimAndFire, 250, numRepeats);
}

Conclusão

Agora, você tem um jogo da Web completo que funciona em computadores, smartphones e tablets. Há muitas melhorias possíveis para esse jogo. Ele parecerá um pouco estranho no modo retrato em um smartphone, por exemplo, você precisa certificar-se de que está segurando o telefone em paisagem para funcionar corretamente. Isso é apenas uma pequena demonstração das possibilidades para o desenvolvimento de jogos para a Web e muito mais.


Michael Oneppo *é um tecnólogo criativo e antigo gerente de programas da Microsoft na equipe do Direct3D. Seus esforços recentes incluem trabalhar como CTO na tecnologia sem fins lucrativos Library For All e explorar um mestrado no Programa de Telecomunicações Interativas da NYU.*​

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Mohamed Ameen Ibrahim