Criando um jogo em Javascript [5/5] – Pontuação

Que tal finalizar nossa versão do jogo da galinha do Atari?

Fizemos o pano de fundo do jogo, no primeiro post.

A movimentação do personagem principal a partir do teclado foi feita no segundo post.

Alguns carros movimentados automaticamente foram inseridos no terceiro post.

No quarto post, o GAME OVER foi detectado ao ocorrer a colisão dos carros e nosso personagem principal.

Iniciando a pontuação

Vamos criar uma variável pontos para armazenar o número de pontos e também uma função desenhaPontos que, quando invocada, desenha os pontos no canto superior esquerdo da página.

var pontos = 0;
function desenhaPontos(){
    contexto.fillStyle = "black";
    contexto.font="12pt Monospace";
    contexto.fillText(pontos, 5, 20);
}

Na função do setTimeout, precisamos desenhar os pontos atuais. Devemos invocar a função que acabamos de criar depois de desenhar o fundo:

setInterval(function(){
    desenhaFundo();
    desenhaPontos();
    dilminha.desenhaImagem();
    carrinhoAmarelo.desenhaImagem();
    carrinhoAzul.desenhaImagem();
    carrinhoPolicia.desenhaImagem();

    //restante do código...
},50);

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/pontuacao/15-zerado.html

Pontuação zerada

Contando os pontos

No jogo do Atari, sempre que a galinha atravessa o lado de cima da rua ganhamos um ponto. Faremos o mesmo.

Quanto mais pra cima tiver o personagem principal, menor é o valor de y. Um valor menor ou igual a zero indica que o personagem atravessou a rua totalmente.

Vamos criar uma função passou que detecta se o personagem principal ultrapassou o limite superior da tela. Já que essa função não valerá pra os carros, vamos colocá-la diretamente como uma propriedade do objeto dilminha.

var dilminha = new Sprite("../../dilminha.png", 320, 400);
dilminha.passou = function(){
    if(this.y <= 0) {
        return true;
    }
    return false;
};

Sempre que houver alguma teclada pressionada no teclado, vamos verificar se a rua foi totalmente atravessada, invocando o método passou que acabamos de definir. Caso o personagem principal tenha ultrapassado o topo da tela, contaremos mais um ponto:

document.onkeydown = function(event) {
    //verificação de game over...
    //tratamento da tecla pressionada...
    if(dilminha.passou()){
        pontos++;
    }
}

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/pontuacao/16-pontos.html

Contagem de pontos

Bug na pontuação

Há um defeito na contagem dos pontos: se o personagem principal deslizar horizontalmente no topo da tela, são contados múltiplos pontos.

Veja:

Defeito na pontuação

Existem várias maneiras de resolver esse problema. Uma das mais simples é movermos o personagem principal lá pra baixo da tela assim que detectarmos que o topo da tela foi ultrapassado. Podemos implementar isso alterando o método passou:

dilminha.passou = function(){
    if(this.y <= 0) {
        this.y = canvas.height - this.altura;
        return true;
    }
    return false;
}

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/pontuacao/17-final.html

Defeito corrigido

Então é isso, pessoal

Poderíamos melhorar muito o joguinho: implementar uma colisão decente, salvar as pontuações em um servidor, fazer funcionar em dispositivos móveis…

Mas vamos parar por aqui! Já deu pra fazer algo razoável. Divertido, não?

Nosso código final ficou assim:

<html>
<body>
  <canvas id="canvas" width="640" height="480" style="border: solid 1px black; margin: 0px auto; display: block;"></canvas>
  <script>
    var gameOver = false;

    var canvas = document.getElementById("canvas");
    var contexto = canvas.getContext("2d");

    function desenhaFundo() {
      //preenche o fundo com cinza escuro
      contexto.fillStyle = "dimgray";
      contexto.fillRect(0, 0, canvas.width, canvas.height);

      //calcada superior
      contexto.fillStyle = "lightgray";
      contexto.fillRect(0, 0, canvas.width, 80);

      //calcada inferior
      contexto.fillStyle = "lightgray";
      contexto.fillRect(0, 380, canvas.width, 100);

      //faixas
      contexto.fillStyle = "white";
      for (var i = 0; i < 25; i++) {
        contexto.fillRect(i * 30 - 5, 185, 20, 4);
        contexto.fillRect(i * 30 - 5, 280, 20, 4);
      }
    }

    var pontos = 0;
    function desenhaPontos() {
      contexto.fillStyle = "black";
      contexto.font = "12pt Monospace";
      contexto.fillText(pontos, 5, 20);
    }

    function Sprite(caminhoDaImagem, xInicial, yInicial) {
      this.x = xInicial;
      this.y = yInicial;

      this.imagem = new Image();
      this.imagem.src = caminhoDaImagem;

      var that = this;
      this.imagem.onload = function () {
        that.largura = that.imagem.width;
        that.altura = that.imagem.height;
        that.desenhaImagem();
      }

      this.desenhaImagem = function () {
        contexto.drawImage(this.imagem, this.x, this.y, this.largura, this.altura);
        contexto.strokeStyle = "darkred";
        contexto.lineWidth = 0.2;
        contexto.strokeRect(this.x, this.y, this.largura, this.altura);
      }

      this.move = function (dx, dy) {
        this.x += dx;
        this.y += dy;

        //limites
        if (this.x > canvas.width) {
          this.x = -this.largura;
        } else if (this.x < -this.largura) {
          this.x = canvas.width;
        }
        if (this.y > canvas.height - this.altura + 5) {
          this.y -= dy;
        } else if (this.y <= -5) {
          this.y = canvas.height - this.altura;
        }
      }

      this.colidiu = function (outro) {
        var colidiuNoXTopo = outro.x >= this.x && outro.x <= (this.x + this.largura);
        var colidiuNoYTopo = outro.y >= this.y && outro.y <= (this.y + this.altura);
        var colidiuNoXBase = (outro.x + outro.largura) >= this.x && (outro.x + +outro.largura) <= (this.x + this.largura);
        var colidiuNoYBase = (outro.y + outro.altura) >= this.y && (outro.y + outro.altura) <= (this.y + this.altura);
        return (colidiuNoXTopo && colidiuNoYTopo) || (colidiuNoXBase && colidiuNoYBase);
      }

    }

    var dilminha = new Sprite("../../dilminha.png", 320, 400);
    dilminha.passou = function () {
      if (this.y <= 0) {
        this.y = canvas.height - this.altura;
        return true;
      }
      return false;
    }

    var carrinhoAmarelo = new Sprite("../../carrinho-amarelo.png", -10, 300);
    var carrinhoAzul = new Sprite("../../carrinho-azul.png", 560, 200);
    var carrinhoPolicia = new Sprite("../../carrinho-policia.png", 10, 100);

    document.onkeydown = function (event) {
      if (gameOver) {
        return;
      }

      switch (event.which) {
      case 37: //pra esquerda
        dilminha.move(-10, 0);
        break;
      case 38: //pra cima
        dilminha.move(0, -10);
        break;
      case 39: //pra direita
        dilminha.move(10, 0);
        break;
      case 40: //pra baixo
        dilminha.move(0, 10);
        break;
      }

      if (dilminha.passou()) {
        pontos++;
      }
    }

    setInterval(function () {
      desenhaFundo();
      desenhaPontos();

      dilminha.desenhaImagem();
      carrinhoAmarelo.desenhaImagem();
      carrinhoAzul.desenhaImagem();
      carrinhoPolicia.desenhaImagem();

      if (gameOver) {
        contexto.fillStyle = "red";
        contexto.font = "Bold 80px Sans";
        contexto.fillText("GAME OVER", canvas.width / 16, canvas.height / 2 + 20);
        return;
      }

      carrinhoAmarelo.move(7, 0);
      carrinhoAzul.move(-5, 0);
      carrinhoPolicia.move(10, 0);

      if (carrinhoAmarelo.colidiu(dilminha) || carrinhoAzul.colidiu(dilminha) || carrinhoPolicia.colidiu(dilminha)) {
        gameOver = true;
      }

    }, 50);
  </script>
</body>
</html>

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/index.html

Criando um jogo em Javascript [4/5] – Colisão

Vamos continuar nossa versão em Javascript do jogo da galinha do Atari.

No primeiro post, fizemos o pano de fundo do jogo.

Já no segundo post, fizemos o personagem principal, movendo-o a partir das setas do teclado e limitando até onde é possível ir.

Depois, no terceiro post, colocamos alguns carros que são movimentados automaticamente.

Agora, vamos detectar a colisão dos carros e nosso personagem principal, ocasionando um GAME OVER.

Detectando a colisão

Tanto o personagem principal como os três carrinhos são objetos criados a partir do construtor Sprite. Todos esses objetos tem as propriedades x, y, largura e altura.

Uma maneira de saber se houve colisão é considerar que as figuras são retângulos e verificar se houve intersecção.

Colisão de retângulos

Vamos definir um método colidiu no construtor Sprite que retorna true se houve intersecção entre os retângulos dos personagens:

function Sprite(caminhoDaImagem, xInicial, yInicial) {
    //restando do código...

    this.colidiu = function(outro){
        var colidiuNoXTopo = outro.x >= this.x && outro.x <= (this.x + this.largura);
        var colidiuNoYTopo = outro.y >= this.y && outro.y <= (this.y + this.altura);
        var colidiuNoXBase = (outro.x + outro.largura) >= this.x && (outro.x + + outro.largura) <= (this.x + this.largura);
        var colidiuNoYBase = (outro.y + outro.altura) >= this.y && (outro.y + outro.altura) <= (this.y + this.altura);
        return (colidiuNoXTopo && colidiuNoYTopo) || (colidiuNoXBase && colidiuNoYBase);
    }
}

Na função do setTimeout, logo após mover os carrinhos vamos verificar se algum colidiu com o personagem principal. Se houve colisão, vamos setar como verdadeira a variável gameOver.

Ainda na mesma função, depois de desenhar o fundo, o personagem principal e os carros, caso o jogo tenha terminado, vamos escrever com uma fonte vermelha bem grande o texto GAME OVER. Para isso, devemos utilizar a função fillText do contexto 2D. Também colocaremos um return antecipado, impedindo que os carros movam-se se o jogo tiver terminado.

Teremos então:

setInterval(function(){
    desenhaFundo();
    dilminha.desenhaImagem();
    carrinhoAmarelo.desenhaImagem();
    carrinhoAzul.desenhaImagem();
    carrinhoPolicia.desenhaImagem();

    if(gameOver){
        contexto.fillStyle = "red";
        contexto.font="Bold 80px Sans";
        contexto.fillText("GAME OVER", canvas.width/16, canvas.height/2+20);
        return;
    }

    carrinhoAmarelo.move(7, 0);
    carrinhoAzul.move(-5, 0);
    carrinhoPolicia.move(10, 0);

    if(carrinhoAmarelo.colidiu(dilminha)
        || carrinhoAzul.colidiu(dilminha)
        || carrinhoPolicia.colidiu(dilminha)){
        gameOver = true;
    }
},50);

Outra coisa que precisamos fazer é impedir que o personagem principal seja movido pelo teclado depois do GAME OVER. Para isso, faremos um retorno antecipado caso a variável gameOver esteja setada.

document.onkeydown = function(event) {
    if(gameOver){
        return;
    }
    //restante do código...
}

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/colidindo/13-colisao.html

Colisão do carro amarelo com o personagem

Desenhando um retângulo nas bordas

Perceba na imagem acima que há uma certa distância na colisão entre o carro amarelo e o personagem principal.

Será que o cálculo do método colidiu está errado? Na verdade, está OK. O problema é que fica difícil de ver onde começa e onde termina o retângulo da imagem.

Por isso, vamos alterar o método desenhaImagem para que seja desenhado uma borda em cada uma das imagens. Assim, teremos um feeback visual do porquê dos objetos estarem colidindo uns com os outros.

O ideal (mas complexo) seria fazer algum algoritmo de colisão mais sofisticado.

O retângulo pode ser desenhado usando a função strokeRect do contexto 2D.

this.desenhaImagem = function(){
    contexto.drawImage(this.imagem, this.x, this.y, this.largura, this.altura);
    contexto.strokeStyle="darkred";
    contexto.lineWidth = 0.2;
    contexto.strokeRect(this.x, this.y, this.largura, this.altura);
}

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/colidindo/14-box.html

Retângulos nas bordas

Tem mais

Implementamos a detecção da colisão entre os carros e o personagem principal. Caso haja colisão, acontece o GAME OVER.

Falta o mais importante do jogo: a pontuação. Será o assunto do próximo post!

Criando um jogo em Javascript [3/5] – Carros

Daremos continuidade à implementação em Javascript de uma versão do jogo da galinha do Atari.

No primeiro post, fizemos o pano de fundo do jogo.

Já no segundo post, fizemos o personagem principal, movendo-o a partir das setas do teclado e limitando até onde é possível ir.

Agora, vamos colocar alguns carros, que poderão colidir com o nosso personagem principal.

Carros

Colocaremos, sobre o nosso fundo, três carros:

Carrinho amareloCarrinho azulCarrinho de polícia

Essas imagens podem ser baixadas em:
http://a-dilminha.appspot.com/carrinho-amarelo.png
http://a-dilminha.appspot.com/carrinho-azul.png
http://a-dilminha.appspot.com/carrinho-policia.png

Precisamos inseri-las na página.

Para o nosso personagem principal, fizemos da seguinte maneira:

function desenhaImagem(){
   contexto.drawImage(imagem, x, y, imagem.width, imagem.height);
};

//personagem principal
var imagem = new Image();
imagem.src = "../../dilminha.png";
var x = 320;
var y = 400;
imagem.onload = desenhaImagem;

Para os carros, devemos fazer coisa parecida, alterando os valores do src, do x e do y. Então, teríamos o seguinte código:

//carrinho amarelo
var imagem2 = new Image();
imagem2.src = "../../carrinho-amarelo.png";
var x2 = -10;
var y2 = 300;
imagem2.onload = desenhaImagem;

//carrinho azul
var imagem3 = new Image();
imagem3.src = "../../carrinho-azul.png";
var x3 = 560;
var y3 = 200;
imagem3.onload = desenhaImagem;

//carrinho de polícia
var imagem4 = new Image();
imagem4.src = "../../carrinho-policia.png";
var x4 = 10;
var y4 = 100;
imagem4.onload = desenhaImagem;

Repare nos números para o x e y em cada imagem. Quanto maior o x, mais para direita. Quanto maior o y, mais para baixo. Assim, o carrinho amarelo fica na faixa de baixo, bem à esquerda (até um pouco fora da tela). Já o carro azul é posicionado na faixa do meio e à direita. O carrinho de polícia fica na fixa de cima, à esquerda.

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/carros/10-carrinhos.html

Imagem da Dilminha sobre o fundo

Organizando o código com objetos

Ao testarmos o código acima, vamos perceber que os carrinhos não foram exibidos! Que coisa! Mas por que?

O que acontece é que tentamos reutilizar o código da função desenhaImagem mas deixamos referências fixas às variáveis x, y e imagem, que são referentes ao personagem principal.

Poderíamos deixar o x, o y e a imagem como parâmetros da função desenhaImagem. Mas vamos usar uma abordagem diferente para organizar o código: objetos.

Essas imagens dos personagens de um jogo são comumente chamadas de sprites.

Então, vamos criar um construtor Javascript chamado Sprite que terá todas a estrutura comum entre o personagem principal e cada um dos carros:

function Sprite(caminhoDaImagem, xInicial, yInicial) {
	this.x = xInicial;
	this.y = yInicial;

	this.imagem = new Image();
	this.imagem.src = caminhoDaImagem;

	var that = this;
	this.imagem.onload = function() {
		that.largura = that.imagem.width;
		that.altura = that.imagem.height;
		that.desenhaImagem();
	}

	this.desenhaImagem = function() {
		contexto.drawImage(this.imagem, this.x, this.y, this.largura, this.altura);
	}
}

Como parâmetro desse construtor recebemos o caminho da imagem, o x inicial e o y inicial. Através do this, guardamos o x e y e criamos uma imagem usando o caminho recebido como parâmetro.

No onload, função chamada assim que uma imagem é carregada, não temos acesso ao this. Por isso, utilizamos um truque muito comum: guardamos o this em uma variável chamada that. A variável that fica acessível porque é carregada na definição da função através do mecanismo chamado de closure.

Para criar o personagem principal e os carros, teremos algo como:

var dilminha = new Sprite("../../dilminha.png", 320, 400);

var carrinhoAmarelo = new Sprite("../../carrinho-amarelo.png", -10, 300);

var carrinhoAzul = new Sprite("../../carrinho-azul.png", 560, 200);

var carrinhoPolicia = new Sprite("../../carrinho-policia.png", 10, 100);

Invocamos o construtor Sprite com o new, para criar os objetos do personagem principal e de cada carro. Além disso, demos nomes melhores às variáveis.

Devemos alterar a função que passamos ao setInterval para que sejam usados os objetos que acabamos de criar:

setInterval(function(){
	desenhaFundo();
	dilminha.desenhaImagem();
	carrinhoAmarelo.desenhaImagem();
	carrinhoAzul.desenhaImagem();
	carrinhoPolicia.desenhaImagem();
},50);

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/carros/11-objetos.html

Dilminha e carros

Movendo o mundo

Temos um problema: nem o personagem principal nem os carros estão movimentando-se. Precisamos efetuar algumas alterações no código.

No construtor Sprite, vamos adicionar uma função move, que recebe as variações das posições x e y e trata os limites:

function Sprite(caminhoDaImagem, xInicial, yInicial) {

	//restante do código...

	this.move = function(dx, dy) {
		this.x += dx;
		this.y += dy;
		//limites
		if(this.x > canvas.width) {
			this.x = -this.largura;
		} else if(this.x < -this.largura) {
			this.x = canvas.width;
		}
		if(this.y > canvas.height - this.altura + 5) {
			this.y -= dy;
		} else if(this.y <= -5) {
			this.y = canvas.height - this.altura;
		}
	}
}

No trecho de código que trata o pressionamento das setas do teclado, vamos usar o objeto do personagem principal:

document.onkeydown = function(event) {
	switch(event.which) {
	case 37: //pra esquerda
		dilminha.move(-10, 0);
		break;
	case 38: //pra cima
		dilminha.move(0, -10);
		break;
	case 39: //pra direita
		dilminha.move(10, 0);
		break;
	case 40: //pra baixo
		dilminha.move(0, 10);
		break;
	}
}

No fim da função do setInterval, vamos fazer com que os carrinhos amarelo e o da polícia movam-se para a direita e com que o azul mova-se para a esquerda (usando um valor negativo). Quanto maior o valor passado, mais rápido o carro será movido:

setInterval(function(){
	//desenha fundo e imagens...

	carrinhoAmarelo.move(7, 0);
	carrinhoAzul.move(-5, 0);
	carrinhoPolicia.move(10, 0);
},50);

Os limites horizontais são tratados na função move do Sprite, fazendo com que os carros apareçam à esquerda ao ultrapassar o limite da direita e vice-versa. O efeito da movimentação é visualizado na próxima chamada da função do setInterval.

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/carros/12-move-tudo.html

Movendo Dilminha e carros

Tem mais

Movemos nosso personagem principal a partir do teclado. E os carros são movidos automaticamente.

Mas veja no gif acima: a colisão do nosso personagem com o carro da polícia não ocasiona nada. Deveria ser um GAME OVER. Esse é o assunto da próxima semana!

Criando um jogo em Javascript [2/5] – Personagem principal

Vamos continuar a nossa versão em JavaScript do Freeway, aquele jogo do Atari da galinha atravessando a rua.

No post anterior, fizemos a rua, calçadas e faixas que ficarão como pano de fundo.

Agora colocaremos a figura do personagem principal.

Inserindo uma imagem

No Freeway criado pelo Henrique Lobo Weissmann, a galinha foi criada a partir de uma matriz de cores (ou bitmap).

Aqui, vamos tomar uma abordagem mais simples: usaremos uma imagem já pronta. Apresento-lhes a Dilminha:

Dilminha, a personagem principal

Essa imagem pode ser baixada em:
http://a-dilminha.appspot.com/dilminha.png

Nota: quem tiver os direitos autorais dessa imagem, por favor entre em contato.

Criaremos um novo Image, com a URL da imagem no atributo src:

var imagem = new Image();
imagem.src = "../../dilminha.png";

Para inserir a imagem no fundo, usaremos o contexto 2D que foi criado a partir do canvas. Passaremos a imagem para a função drawImage do contexto, além das posições iniciais (x e y) e largura e altura da imagem:

var x = 320;
var y = 400;
contexto.drawImage(imagem, x, y, imagem.width, imagem.height);

Se tentarmos executar o código acima, veremos que a imagem não foi exibida. Porque será? É que só podemos colocá-la no contexto depois dela ter sido carregada. Para isso, devemos colocar uma função na propriedade onload da imagem que será executada depois do carregamento:

imagem.onload = function(){
    contexto.drawImage(imagem, x, y, imagem.width, imagem.height);
}

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/movendo/5-figura.html

Imagem da Dilminha sobre o fundo

Movendo o personagem

Com a imagem inserida sobre o fundo, precisamos movê-la ao pressionar as setas do teclado. Para isso, vamos passar para a propriedade onkeydown do document uma função que será chamada quando algo for digitado:

document.onkeydown = function(){
    //executado quando algo for digitado...
}

Nessa função, recebemos como parâmetro um objeto que representa o evento do pressionamento de teclas. Através da propriedade which desse evento, podemos saber qual tecla foi pressionada. A seta pra esquerda tem o código 37; pra cima, o código é 38; pra direita, 39; para baixo, 40.

Os eixos x e y são contados da esquerda pra direita e de baixo pra cima. Quanto maior o valor, mais para a direita e mais para baixo estará a imagem. Por isso, mover para a direita é aumentar o valor de x; pra esquerda é diminuir. Mover para cima, é diminuir o y; pra baixo, é aumentar.

Então, ficaríamos com o código:

document.onkeydown = function(event) {
    switch(event.which) {
        case 37: //pra esquerda
            x = x - 10;
        break;
        case 38: //pra cima
            y = y - 10;
        break;
        case 39: //pra direita
            x = x + 10;
        break;
        case 40: //pra baixo
            y = y + 10;
        break;
    }
}

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/movendo/6-move.html

Mas não moveu...

Por que não moveu?

Ao executarmos o código acima, vamos perceber que a imagem não foi movida. Será que esse código não funciona?

Bom… Quando o usuário pressiona as setas do teclado, o x e o y foram atualizados. O que precisamos fazer é redesenhar a imagem periodicamente, para que as novas posições sejam exibidas.

Para executar algo periodicamente no JavaScript, devemos usar a função setInterval. Precisam ser passados como parâmetros a função que será chamada e o número de milissegundos de espera entre as chamadas.

No nosso caso, a função terá o trecho de código que desenha a imagem no contexto 2D. Serão utilizados o x e o y calculados quando o usuário pressiona alguma seta do teclado. Nosso intervalo de espera será de 50 milissegundos:

setInterval(function(){
    contexto.drawImage(imagem, x, y, imagem.width, imagem.height);
}, 50);

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/movendo/7-recarrega.html

Redesenha a imagem a cada 50 milissegundos

Limpando o fundo

Perceba na imagem anterior que é deixado um rastro de todas as posições da nossa imagem. Isso acontece porque estamos redesenhando a imagem nas novas posições se limpar as imagens anteriores.

Uma maneira de fazer isso é desenhar o fundo novamente toda vez que formos atualizar a posição da imagem.

Para poder reaproveitar o código que desenha o fundo, vamos colocá-lo dentro de uma função chamada desenhaFundo. Essa função será invocada imediatamente, logo que a página for carregada, além de periodicamente, a cada 50 ms.

Vamos aproveitar e criar uma função desenhaImagem, que será responsável por desenhar a imagem no contexto 2D. Essa função também será invocada imediatamente e periodicamente.

O código reorganizado ficaria assim:

function desenhaFundo () {
    //preenche o fundo com cinza escuro
    contexto.fillStyle = "dimgray";
    contexto.fillRect(0, 0, canvas.width, canvas.height);

    //calcada superior
    contexto.fillStyle = "lightgray";
    contexto.fillRect(0, 0, canvas.width, 80);

    //calcada inferior
    contexto.fillStyle = "lightgray";
    contexto.fillRect(0, 380, canvas.width, 100);

    //faixas
    contexto.fillStyle = "white";
    for(var i = 0; i < 25; i++){
        contexto.fillRect(i*30-5, 185, 20, 4);
        contexto.fillRect(i*30-5, 280, 20, 4);
    }
}

function desenhaImagem(){
    contexto.drawImage(imagem, x, y, imagem.width, imagem.height);
};

var imagem = new Image();
imagem.src = "../../dilminha.png";
var x = 320;
var y = 400;
imagem.onload = desenhaImagem;

//código que trata teclas pressionadas omitido...

setInterval(function(){
    desenhaFundo();
    desenhaImagem();
},50);

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/movendo/8-limpando.html

Movendo imagem depois de limpar fundo

Impondo limites

Do jeito que foi feito o código que trata as teclas digitadas, os valores de x e y são adicionados ou subtraídos indefinidamente. Isso faz com que não existam limites e a imagem desapareça da tela. Por exemplo:

Imagem quase sumindo do canto inferior direito da tela

Seria interessante que limitássemos os valores possíveis de x e y. Por exemplo, podemos definir um limite superior e fazer com que, quando ultrapassado, a imagem aparece na parte debaixo da tela. Poderíamos criar também limites horizontais: quando a imagem ultrapassar o limite da direita, aparecerá a esquerda e vice-versa. O limite inferior será fixo, não fazendo com que seja tomado um atalho para o topo da tela.

Uma implementação desses limites, seguindo as regras acima, seria:

document.onkeydown = function(event) {
    switch(event.which) {
        case 37: //pra esquerda
            x = x -10;
            //passado o limite da esquerda, aparecerá à direita
            if(x < -imagem.width) {
                x = canvas.width;
            }
        break;
        case 38: //pra cima
            y = y - 10;
            //passado o limite superior, aparecerá embaixo
            if(y <= -5) {
                y = canvas.height - imagem.height;
            }
        break;
        case 39: //pra direita
            x = x + 10;
            //passado o limite da direita, aparecerá à esquerda
            if(x > canvas.width) {
                x = -imagem.width;
            }
        break;
        case 40: //pra baixo
            y = y + 10;
            //se o novo y passou o limite inferior, desfaz a soma
            if(y > canvas.height - imagem.height + 5) {
                y -= 10;
            }
        break;
    }
}

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/movendo/9-limites.html

Limite inferior

Tem mais

Semana que vem, teremos carros que se moverão sozinhos. E iremos organizar um pouco o código. Até mais!

Criando um jogo em Javascript [1/5] – Fundo

Por que a galinha atravessou a rua?

Certa vez, durante uma de minhas aulas, um aluno disse que um dos seus objetivos era fazer um joguinho. Pergunto se poderia ajudá-lo. Claro!

Jogos são complicados de programar. Muito esforço e conhecimento são necessários.

Pensei sobre um jogo simples, pra facilitar a implementação o máximo possível. Pelas restrições da tecnologia da época, os jogos do Atari não tinha grandes complicações. Aí, lembrei de um post em que Henrique Lobo Weissmann implementava o Freeway, aquele jogo da galinha atravessando a rua do Atari, manipulando um canvas do HTML5 com Javascript.

Resolvi fazer a minha versão, com uma abordagem e um tema um pouco diferentes.

Cenário com canvas do HTML5

Para fazer o cenário do jogo, vamos utilizar a tag canvas do HTML5:

<html>
  <body>
    <canvas id="canvas" width="640" height="480"
      style="border: solid 1px black; margin: 0px auto; display: block;"></canvas>
  </body>
</html>

É importante declarar a largura (width) e altura (height) diretamente no canvas, para que os desenhos não fiquem distorcidos. Através do atributo style, adicionamos uma borda, retiramos as margens superior e inferior e fizemos o canvas ocupar a linha toda. Não é a maneira ideal de declarar CSS, mas pra gente é o suficiente…

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/iniciando/1-canvas.html.

1-canvas

Colorindo o fundo com JS

Vamos colorir o canvas com um cinza escuro, para que seja o asfalto do nosso cenário.

Para isso, utilizaremos JavaScript, declarado dentro de uma tag script:

<html>
  <body>
    <canvas ...></canvas>
    <script>
      //nosso js aqui...
    </script>
  </body>
</html>

Nosso código JavaScript ficará diretamente no HTML. Não é o ideal, mas serve para o nosso caso…

Dentro da tag script, vamos obter o objeto DOM do canvas pelo id e, a partir desse, um context 2D, que podemos utilizar para desenhar pontos, retângulos, pixels e outras coisas.

var canvas = document.getElementById("canvas");
var contexto = canvas.getContext("2d");

A partir do context, vamos configurar a propriedade fillStyle com um cinza escuro e desenhar um retângulo, preenchendo todo a largura e altura do canvas, com a função fillRect.

//preenche o fundo com cinza escuro
contexto.fillStyle = "dimgray";
contexto.fillRect(0, 0, canvas.width, canvas.height);

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/iniciando/2-fundo.html

2-fundo

Desenhando as calçadas

Usaremos um cinza mais claro para preencher um retângulo no topo e um no fim do canvas que serão as calçadas:

//calcada superior
contexto.fillStyle = "lightgray";
contexto.fillRect(0, 0, canvas.width, 80);

//calcada inferior
contexto.fillStyle = "lightgray";
contexto.fillRect(0, 380, canvas.width, 100);

Os parâmetros da função fillRect são:

  • o x inicial, da esquerda para a direita
  • o y inicial, do topo para baixo
  • a largura do retângulo em px
  • a altura do retângulo em px

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/iniciando/3-calcadas.html

3-calcadas

Desenhando as faixas da rua

Através do fillRect, vamos desenhar pequenos retângulos brancos de 20 px de largura e de 4 px de altura. Teremos duas faixas, uma superior e uma inferior, separando a rua em três pistas.

//faixas
contexto.fillStyle = "white";
for(var i = 0; i < 25; i++){
  //faixa superior
  contexto.fillRect(i*30-5, 185, 20, 4);

  //faixa inferior
  contexto.fillRect(i*30-5, 280, 20, 4);
}

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/iniciando/4-faixas.html

4-faixas

Tem mais

Fique ligado(a), que não acabamos ainda! Esse é apenas o primeiro de uma série de posts!