Plinko usando P5.js e Matter.js

Plinko usando P5.js e Matter.js

Nesse post vou mostrar passo-a-passo como codificar o jogo Plinko usando nosso querido P5.js e usando uma biblioteca muito legal para simular física chamada Matter.js.

Contextualizando…

O jogo “Plinko” aqui no Brasil não é muito conhecido por esse nome, a verdade é que eu mesmo nem sabia o nome dele até começar a escrever esse post. Apesar do nome não ser popular, nosso querido Brasil já explorou bastante esse jogo naqueles brinquedinhos de camelô antigos que nós comprávamos (antes da internet existir).

Para vocês entenderem do que eu estou falando… esse é o jogo Plinko:

Interface do game disponível no site GameForge

Além disso, não podemos esquecer que alguns programas de TV também já usaram esse game, por exemplo, na quarta temporada de Stranger Things (quando a Eleven brinca na sala de jogos do laboratório) ou então o Caldeirão do Hulk que lançou um quadro chamado “The wall”, cujo os participantes jogavam fichas que ao cair nas casas eles ganhavam dinheiro.

A TV aberta brasileira mostra pessoas pobres lutando para ganhar um pouco de dinheiro baseado em “sorte” (o que não é novidade). Porém, podemos dizer que esse tabuleiro possui um pouco de ciência… ele foi estudado por estatísticos e principalmente por Sir Galton.

File:Francis Galton 1850s.jpg
Esse é o Sir Galton
File:Galton box 2.jpg
Esse é o tabuleiro do Sir Galton

O tabuleiro de Galton mostra um fenômeno estatístico chamado normalidade que é muito interessante. Apesar de parecer meio inútil, ele é capaz de demonstrar (cientificamente falando) porque é uma besteira falar de meritocracia quando o ponto de partida não é igual para todos. Esse vídeo do Átila Iamarino mostra como isso funciona…

As regras do jogo

Vamos para o que interessa… vamos definir quais serão as regras do nosso jogo.

  • O tabuleiro deverá possuir uma zona de lançamento, somente nessa zona as bolinhas podem ser lançadas.
  • Os obstáculos possuem tamanhos aleatórios desiguais (para dificultar um pouco mais a cada partida)
  • Existe uma área de pontuação no fim do tabuleiro com 10 “casas”
  • Cada casa de pontuação possui um número de pontos atribuidos aleatóriamente a cada partida.
  • Ao cair em determinada casa, os pontos do jogador devem ser somados e mostrados em algum lugar da tela.
  • O jogador deve estar limitado a jogar apenas 3 bolinhas por partida
  • As laterais não possuem paredes e a bola pode “vazar” por elas.

Passo 1 – Desenhando a interface

Nosso primeiro passo é desenhar no P5.js a interface do game. Esse é nosso objetivo:

Para desenhar isso usando o P5.js definimos as seguintes funções:

function setup() {
  createCanvas(500, 500);
  
}

function draw() {
  background(240);
  drawPointsArea();
  drawDropArea();
 
}

// draw interface
function drawDropArea() {
  rectMode(CORNER);
  c = color("rgba(0, 0, 255, 0.2)");
  fill(c);
  rect(0, 0, 500, 60);
  textSize(40);
  text("Drop Area", 250, 45);
}

function drawPointsArea() {
  rectMode(CORNER);
  c = color("rgba(255, 0, 0, 0.2)");
  fill(c);
  rect(0, 500 - 60, 500, 60);

  rectMode(CENTER);

  let posX = -44;
  for (let i = 0; i < 12; i++) {
    rect(posX, 470, 10, 60);
    posX = posX + 49;
  }
  
  let txtPosX = -50;
  fill("black");
  textSize(25);
  textAlign(CENTER);
}

Um pouco de física com Matter.js

Agora que temos nossa interface praticamente “pronta” podemos trabalhar sobre ela para adicionar um pouco de física a esse game. Para isso teremos que visitar a documentação do Matter.js. Essa biblioteca tem como objetivo principal codificar a física de uma forma símples e que pode ser usada dentro dos seus códigos javascript.

Existe um detalhe importante a ser considerado aqui… o Matter.js possui um “renderer” próprio, isso significa que ele já disponibiliza aos seus usuários uma forma própria de mostrar os objetos criados na tela. Porém, em nosso game estamos usando o P5.js, sendo assim, nosso renderer será o P5 e vamos ignorar o renderer padrão.

O primeiro passo para adicionar o Matter.js em nosso projeto é adicionar a biblioteca no HTML:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/addons/p5.sound.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
    <link rel="stylesheet" type="text/css" href="style.css" />
    <meta charset="utf-8" />
  </head>
  <body>
    <main></main>
    <script src="sketch.js"></script>
  </body>
</html>

Apenas com essa mudança você pode usar os objetos do Matter em seu código. Mas para isso é preciso criar algumas variáveis e objetos padrão da biblioteca:

var Engine = Matter.Engine,
  Runner = Matter.Runner,
  World = Matter.World,
  Bodies = Matter.Bodies,
  Composite = Matter.Composite;


function setup() {
  createCanvas(500, 500);
  engine = Engine.create();
  world = engine.world;
  Runner.run(engine);
}

O código acima é suficiente para “instanciar” o seu Matter.js, mas calma… apenas com esse código você não verá nada na tela visto que os objetos (também chamados de bodies) estão sem o renderer padrão e você também não codificou nada para mostrar esses corpos. Então, o próximo passo é esse… codificar corpos que sejam ao mesmo tempo objetos do Matter.js e do P5.js:


// Objects
function Ball(x, y, r) {
  this.body = Bodies.circle(x, y, r / 2);
  World.add(world, this.body);

  this.show = function () {
    var pos = this.body.position;
    circle(pos.x, pos.y, r);
  };
}

function Obstacle(x, y, r) {
  this.body = Bodies.circle(x, y, r / 2, { isStatic: true });
  this.r = r;
  World.add(world, this.body);

  this.show = function () {
    var pos = this.body.position;
    var angle = this.body.angle;
    circle(x, y, r);
  };
}

Os objetos apresentados possuem na sua primeira linha a invocação de Bodies.circle(param). Esse trecho cria um novo corpo que possui física, ou seja, um objeto que o Matter consegue lidar. A seguir, a próxima linha adiciona esse objeto ao “World”, isso significa que aquele objeto está pertencendo aquele mundo que você criou usando o Matter. Por fim, você pode codificar a função show() que irá mostrar o objeto exatamente na posição em que o Matter.js acredita que ele está.

Atenção: lembre-se que você aqui está integrando duas bibliotecas que são independentes. Isso significa se você codificar a função show() com parâmetros errados isso pode mostrar na tela objetos que para o Matter possui uma dimensão, mas são mostrados de maneira diferente. Isso aconteceu bastante comigo ao implementar esse game… fique atento!

Jogando as bolinhas no tabuleiro

O game é todo baseado no mouse… então, o jogador só precisa escolher onde ele vai “jogar” sua bolinha. Para isso usamos a função mousePressed().

function mousePressed() {
  if (
    mouseX >= 0 &&
    mouseX <= 500 &&
    mouseY >= 0 &&
    mouseY <= 60 &&
    availableBalls > 0
  )
    balls.push(new Ball(mouseX, mouseY, 10));
  availableBalls--;
}

Vale lembrar que essas bolinhas são adicionadas a um array de bolinhas que foi declarado com escopo global.

Gerando os obstáculos

Os obstáculos do plinko são essenciais para o jogo. Eles na verdade são corpos rígidos que possuem física (colisão) e precisam ser gerados e dispostos no tabuleiro:


// generation functions
function generateObstacles() {
  let posX = 0;
  let posY = 50;
  let distortion = 0;
  for (let i = 0; i < 7; i++) {
    posY = posY + 50;
    posX = 30;
    if (i % 2 == 0) {
      distortion = 25;
    } else {
      distortion = 0;
    }
    for (let j = 0; j < 9; j++) {
      obstacles.push(new Obstacle(posX + distortion, posY, random(20, 30)));
      posX = posX + 50;
    }
  }
}

Gerando o quadro de pontos

Para o jogo ficar mais interessante, podemos gerar nosso quadro de pontos aleatoriamente. Assim, cada partida será diferente e o jogo torna-se mais divertido. Para isso, vamos usar a seguinte função:

function generateScoreboard() {
  let scoreboard = [];
  for (i = 0; i < 10; i++) {
    scoreBoard.push(floor(random(0, 10)));
  }
  return scoreBoard;
}

Lembre-se que também é preciso desenhar essa pontuação dentro do seu tabuleiro:

for (let i = 0; i < 10; i++) {
   txtPosX = txtPosX + 49;
   text(scoreBoard[i], txtPosX + 30, 480);
}

Gerando os limites do tabuleiro do plinko

Os limites do tabuleiro como por exemplo o “chão” onde as bolinhas caem ou então as laterais das casinhas precisam também ser corpos rígidos. Portanto precisamos criar esses corpos:


function generateGround() {
  // rectMode(CENTER);
  // rect(width / 2, 500, 500, 20)
  let ground = Bodies.rectangle(250, 525, 500, 50, { isStatic: true });
  World.add(world, ground);
}

Calculando a pontuação do jogador

A pontuação do jogador deve ser calculada conforme a posição onde a bolinha parou. Mas a grande complicação desse game é que essa pontuação é calculada com base em um vetor que é inicialmente aleatório. Portanto usamos as seguintes funções para delimitar onde a bolinha está e somar os pontos do jogador.


function generateBounds() {
  let sb = [];
  let posX = -44;
  for (let a = 0; a < 12; a++) {
    // console.log(a);
    let boundLeft = posX + 44;
    posX = posX + 49;
    let boundRight = posX + 44;
    sb.push({ boundLeft, boundRight });
  }
  return sb;
}

function calculateScore() {
  let score = 0;

  for (i = 0; i < balls.length; i++) {
    for (j = 0; j < scoreBounds.length; j++) {
      //console.log(balls[i].body.position.x, scoreBounds[i].boundLeft, scoreBounds[i].boundRight);
      if (
        balls[i].body.position.y > 400 &&
        balls[i].body.position.x > scoreBounds[j].boundLeft &&
        balls[i].body.position.x < scoreBounds[j].boundRight
      ) {
        score = score + scoreBoard[j];
      }
    }
  }
  return score;
}

Resultado final

Finalmente podemos juntar tudo isso e ver como fica nosso jogo:

Live Demo

Veja nosso projeto completo:

Vinicius dos Santos

Apenas um apaixonado por Ciência da Computação e a forma com que ela pode transformar vidas!

Deixe um comentário

três × 2 =