Javascript episódio 2

Continuando no Javascript, vamos olhar como funciona as coordenadas, o sistema de coordenadas no canvas. Lembro que vi isso pela primeira vez no livro de java do Robert Sedgewick, porque ele fazia o próprio sistema de gráficos dele, bem simples, para fazer histogramas e gráficos de pontos, mas la a gente ve que diferente de como a gente pensa, o monitor desenha da parte superior esquerda, para baixo e para direta, então é como se fosse tudo uma mega matriz de pixels, e o (0,0) esta no canso superior esquerdo, pra quem esta olhando a tela. Com arduino, tem um projetinho que você projeta a hora na tv, via cabo coaxial, e você tem que fazer umas contas nesses sentido, mas uma coisa é pintar meia duzia de pixel para fazer um número, controlar uma matriz de led, outra é fazer uma imagem, mas chega de divagar, vamos continuar aquele exemplo, vamos olhar algumas partes agora.

No post anterior, a gente colocou tudo no arquivo html, e ele ficou grande, colocamos o elemento style e os scripts de javascript tudo junto, mas isso começa a complicar, então o que vamos fazer é separar tudo em arquivos diferentes, para web, a gente pode fazer mais de um arquivo na mesma pasta, para facilitar, e usamos arquivos .css para o código do estilo da página e os arquivos .js para scripts de javascript.

Ai como você faz para separar, eu vou ter dois arquivos, o java02.css e o java02.html, o tudo que estava dentro da tag style, eu vou colocar dentro do java02.css, que fica assim:

$ cat java02.css body { text-align: center; font-family: sans-serif; } canvas { background-color: black; }

E no arquivo java02.html, vamos adicionar uma linha no para chamar esse arquivo assim:

1
<link rel="stylesheet" href="java02.css">

E pronto, ja diminuímos um pouco o tamanho de tudo. E agora, vamos continuar com aquela mesma estrutura de html, com um canvas. Então o java02.html vai ser:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!doctype html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
    <title>Desenhando no canvas html</title>
    <link rel="stylesheet" href="java02.css">
 
  </head>
  <body>
 
    <h1>Desenhando no canvas html</h1>
    <canvas id="asteroids" width="400" height="400"></canvas>
 
    <script>
    //Meu script
    </script>
 
 
  </body>
</html>

E vamos fazer um script, primeiro para entender como funciona esse grid.

Precisamos sempre a referência do elemento para trabalhar

1
2
var canvas = document.getElementById("asteroids");
var context = canvas.getContext("2d");

Mas para desenhar, vamos fazer uma função, colocando em outro arquivo, para depois utilizar, então vamos criar um arquivo chamado biblioteca.js, vamos referenciar ele, parecido com o que fizemos com o arquivo css, na linha depois dela dentro do

1
<script src="biblioteca.js"></script>

Agora dentro desse aquivos, vamos criar uma função de javascript da seguinte forma:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function desenha_grid(ctx, minor, major, stroke, fill) {
 
    minor = minor || 10;
    major = major || minor * 5;
    stroke = stroke || "#00FF00";
    fill = fill || "#009900";
    ctx.save();
    ctx.strokeStyle = stroke;
    ctx.fillStyle = fill;
 
    let width = ctx.canvas.width, height = ctx.canvas.height
 
    for(var x = 0; x < width; x += minor) {
	ctx.beginPath();
        ctx.moveTo(x, 0);
        ctx.lineTo(x, height);
        if(x % 50 == 0){
            ctx.lineWidth = (0.5);
	}else{
           ctx.lineWidth = (0.25);
        }
        ctx.stroke();
        if(x % major == 0 ) {ctx.fillText(x, x, 10);}
    }
    for(var y = 0; y < height; y += minor) {
        ctx.beginPath();
        ctx.moveTo(0, y);
        ctx.lineTo(width, y);
        ctx.lineWidth = (y % major == 0) ? 0.5 : 0.25;
        ctx.stroke();
        if(y % major == 0 ) {ctx.fillText(y, 0, y + 10);}
    }
    ctx.restore();
}

Então essa é uma função, chamada desenha_grid, que aceita cinco argumentos, o primeiro é o contexto, o canvas que a gente quer desenhar, e o outros são de quanto em quanto pixels a gente quer desenhar linhas, assim como num papel milimetrados, temos linhas pequenas e linhas mais grossas, esses são os tamanhos em pixel, e o stroke e fill são as cores de preenchimento.

O jeito mais simples de usar é apenas falar qual o contexto, que temos argumentos de tamanhos e cores pré-definidos, então o seguinte arquivo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!doctype html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
    <title>Desenhando no canvas html</title>
    <link rel="stylesheet" href="java02.css">
    <script src="biblioteca.js"></script>
 
  </head>
  <body>
 
    <h1>Desenhando no canvas html</h1>
    <canvas id="asteroids" width="400" height="400"></canvas>
 
    <script>
      var canvas = document.getElementById("asteroids");
      var context = canvas.getContext("2d");
      // Desenhando no Canvas
 
      desenha_grid(context);
 
    </script>
 
  </body>
</html>

Vai produzir isso:

Legal, veja que podemos chamar a função com outras cores para produzir outras coisas. Por exemplo

1
desenha_grid(context, 15, 45, 'red', 'yellow');

Vai produzir

Ok, agora que a gente já viu o que a função faz vamos entender como ela funciona.

A estrutura geral para definir uma função no javascript é assim:

1
2
3
function desenha_grid(ctx, minor, major, stroke, fill) {
Seu código
}

Não precisamos definir o tipo dos argumentos, so dar nomes, ai o código da função é o seguinte

1
2
3
4
    minor = minor || 10;
    major = major || minor * 5;
    stroke = stroke || "#00FF00";
    fill = fill || "#009900";

Com exceção do contexto, de onde desenhar, que não tem como ser padrão, precisamos de um canvas para desenhar, todos os outros argumentos, se não forem definidos na função, definimos eles aqui, veja que eles são pegos na ordem que são fornecidos na função.

Depois, antes de alterar o contexto, salvamos todos os parâmetros deles

1
    ctx.save();

Depois que temos salvo os parâmetros, começamos a mexer neles,

1
2
    ctx.strokeStyle = stroke;
    ctx.fillStyle = fill;

Definimos duas váriaveis, altura e largura, que vamos usar para fazer o grid

1
    let width = ctx.canvas.width, height = ctx.canvas.height

E então vamos fazer dois loops

1
2
3
4
5
6
7
8
9
10
11
12
    for(var x = 0; x < width; x += minor) {
	ctx.beginPath();
        ctx.moveTo(x, 0);
        ctx.lineTo(x, height);
        if(x % major == 0){
            ctx.lineWidth = (0.5);
	}else{
           ctx.lineWidth = (0.25);
        }
        ctx.stroke();
        if(x % major == 0 ) {ctx.fillText(x, x, 10);}
    }

Aqui para a altura, vamos fazer linhas, então começamos a linhas no (0,0), vamos com ela até la em baixo, já que height foi definido ali em cima como o tamanho da tela, e definimos o lineWidth fina para cada linha minor, e mais grossa para o major, que são argumentos para o grid, usando um if, depois o ctx.stroke() desenha a linha e para cada vez que chegamos no major, escrevemos em que pixel estamos com o fillText. Depois repetimos o mesmo esquema para as linhas horizontais

1
2
3
4
5
6
7
8
    for(var y = 0; y < height; y += minor) {
        ctx.beginPath();
        ctx.moveTo(0, y);
        ctx.lineTo(width, y);
        ctx.lineWidth = (y % major == 0) ? 0.5 : 0.25;
        ctx.stroke();
        if(y % major == 0 ) {ctx.fillText(y, 0, y + 10);}
    }

So dando um destaque aqui, para essa forma mais resumida de usar if e else, acho que é o mesmo esquema que da pra usar no c++ e no c, mas que a gente não vê em R e python, se bem que python tem maneiras bem sintéticas de escrever as coisas, mas a primeira vez que a gente vê pode ser confuso, mas tem a mesma coisa usando if else ali em cima só para facilitar o entendimento.

Bem, feito isso, como mudamos os parâmetros do contexto, e la em cima salvamos como estava antes, voltamos eles com o restore()

1
    ctx.restore();

E agora é como se nem tivéssemos mexidos. Esse grid vai ajudar muito a entender melhor as coordenadas, da para entender melhor como são feitas as retas e o preenchimento agora.

Vamos fazer algumas retas

1
2
3
4
5
6
7
8
9
10
11
      context.beginPath();
      context.strokeStyle = "#FFFFFF";
      context.fillStyle = "#00FF00";
      context.lineWidth = 2;
      context.moveTo(50, 50);
      context.lineTo(150, 250);
      context.lineTo(250, 170);
      context.stroke();
      context.fillText("(50, 50)", 30, 40);
      context.fillText("(150, 250)", 130, 260);
      context.fillText("(250, 170)", 255, 175);

Começamos um caminho, fomos para o ponto (50, 50) dai fazemos uma reta para (150, 250) e depois (250, 170), e vamos escrever esses pontos, um pouco deslocado do local sendo desenhado com fillText, para se ligar no que estamos fazendo, lembrando que só o lineTo não desenha nada, so depois do stroke o desenho aparece na tela.

Agora diferente do texto, Stroke desenha a linha, mas podemos preencher essa linha, que como não é uma forma fechada, fica assim.

1
2
3
4
5
6
7
8
9
10
11
12
13
      //Reta inicial
      context.beginPath();
      context.strokeStyle = "#FFFFFF";
      context.fillStyle = "#00FF00";
      context.lineWidth = 2;
      context.moveTo(50, 50);
      context.lineTo(150, 250);
      context.lineTo(250, 170);
      context.stroke();
      context.fillText("(50, 50)", 30, 40);
      context.fillText("(150, 250)", 130, 260);
      context.fillText("(250, 170)", 255, 175);
      context.fill();

Que fica assim:

Então o que acontece, é que a forma sempre é fechada na origem e preenchida, caso ela não esteja fechada, por exemplo se expandirmos as linhas assim

1
2
3
4
5
6
7
8
9
10
11
12
13
14
      //Reta inicial
      context.beginPath();
      context.strokeStyle = "#FFFFFF";
      context.fillStyle = "#00FF00";
      context.lineWidth = 2;
      context.moveTo(50, 50);
      context.lineTo(150, 250);
      context.lineTo(250, 170);
      context.lineTo(320, 280);
      context.stroke();
      context.fillText("(50, 50)", 30, 40);
      context.fillText("(150, 250)", 130, 260);
      context.fillText("(250, 170)", 255, 175);
      //context.fill();

quando preenchemos com o fill temos

Mas para fecharmos nossas forma, e fazer um triângulo por exemplo, podemos usar o closePath

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//Triângulos
      context.beginPath()
      context.moveTo(50, 250);
      context.lineTo(50, 350);
      context.lineTo(150, 350);
      context.closePath();
      context.moveTo(230, 360);
      context.lineTo(270, 360);
      context.lineTo(270, 310);
      context.closePath();
      context.moveTo(250, 50);
      context.lineTo(370, 50);
      context.lineTo(370, 100);
      context.closePath();
      context.strokeStyle = "#FFFF00";
      context.fillStyle = "#000000";
      context.fill();
      context.stroke();

Que da.

Mas estrada sem curvas da sono não é verdade, existe algumas formar de curvarmos um poucos as formas, uma é usando o quadraticCurveTo, que é uma equação de segundo grau, basicamente, então o ponto inicial e final são as duas raizes e temos os outros termos da equação.

Nesse caso, a gente fornece mais dois pontos,

context.quadraticCurveTo(cpx,cpy,x,y);

cpx e cpy são um ponto de controle, e o x e y são a outra raiz, dessa forma

É equivalente a

1
2
moveTo(20,20)
quadraticCurveTo(20,100,200,20)

Então esse código

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
      //Curvas Quadráticas
      context.beginPath()
      context.moveTo(50, 250);
      context.quadraticCurveTo(25, 300, 50, 350);
      context.quadraticCurveTo(100, 375, 150, 350);
      context.closePath();
      context.moveTo(230, 360);
      context.quadraticCurveTo(255, 340, 270, 360);
      context.quadraticCurveTo(255, 340, 270, 310);
      context.closePath();
      context.moveTo(250, 50);
      context.quadraticCurveTo(310, 60, 370, 50);
      context.quadraticCurveTo(400, 75, 370, 100);
      context.closePath();
      context.strokeStyle = "#FFFF00";
      context.fillStyle = "#000000";
      context.fill();
      context.stroke();

da isso.

Legal, mas no manual do w3schools para quadraticCurveTo a gente já vê sobre o bezierCurveTo(), que é parecido, mas usamos dois pontos de referencia, para entortar diferente as curvas.

a sintaxe vai pedir dois pontos de controle

context.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y);

E o codigo

1
2
moveTo(20,20)
bezierCurveTo(20,100,200,100,200,20)

Vai produzir o seguinte

Então podemos modificar aquelas primeiras retas e usar o closePath

1
2
3
4
5
6
7
8
9
10
11
//Curvas Bezier
      context.beginPath();
      context.strokeStyle = "red";
      context.fillStyle = "#00FF00";
      context.lineWidth = 2;
      context.moveTo(50, 50);
      context.bezierCurveTo(0, 0, 80, 250, 150, 250);
      context.bezierCurveTo(250, 250, 250, 250, 250, 170);
      context.bezierCurveTo(250, 50, 400, 350, 320, 280);
      context.closePath();
      context.stroke();

Para produzir

Sendo que o closePath continua sendo uma reta.

Bem é isso ai, já da para entender mais alguma coisinha de Javascript agora, tudo ficando mais organizado, o script vai estar la no repositório de javascript, e se eu escrevi alguma bobeira, algo errado, deixe um comentário corrigindo ou mande um e-mail.

Referência:
Graeme Stuart 2017 – Introducing JavaScript Game Development Build a 2D Game from the Ground Up. Apress 221pp

Leave a Reply

Your email address will not be published. Required fields are marked *