foto Jean Tux jeaanca
Jean Tux

Bilhar Game

Bilhar game developed with javascript

projeto completo: https://github.com/jeantux/bilhar-game

Clique aqui para jogar online

Esse jogo é resultado de uma série de estudos que iniciei em jogos em 2020 com javascript.

É inevitável construir esse game sem entender conceitos fantásticos, como colisão e comportamento de objetos, organização de código, abstraindo algumas camadas é possível entender cálculos matemáticos de forma divertida.

Essa é uma versão simples de um game offline.

Existem diversas alterações que podem ser feitas para otimizar, por exemplo, criar uma versão multiplayer e coisas do gênero.

Construção dos gráficos

Eu escolhi usar a biblioteca p5.js para facilitar a construção dos elementos gráficos, mais poderíamos facilmente usar o canvas puro.

essa biblioteca disponibiliza alguns eventos, foi utilizado alguns deles, como, por exemplo:

por exemplo, essa é a estrutura básica inicial de um projeto:

function setup() {
  // configuração inicial
  createCanvas(400, 400);
}

function draw() {
  background(255);
  // Suas animações vão aqui
}

Outro evento que não comentei mais foi utilizado é o mouseClicked que captura a posição do cursor, uso esse evento para controlar o taco.

No projeto, cada elemento chave foi separado em um arquivo com sua própria "classe".

Isso faz com que conseguimos gerar a mesa em menos de 70 linhas.

A construção dos elementos não é algo complicado, por possuir um gráfico simples, basicamente posicionamos circulos e retângulos dentro de um espaço e vamos colorindo para gerar os objetos, logicamente essa é uma forma simples de descrever o desenho dos elementos.

Veja o placar, que é um elemento extremamente básico, segue abaixo o código:

// https://github.com/jeantux/bilhar-game/blob/master/scripts/score.js
function Score() {
    this.score = 0

    return {
        getScore: () => {
            return this.score
        },
    
        add: (score) => {
            this.score += score
        },
    
        clear: () => {
            this.score = 0
        },
    
        display: () => {
            fill(0)
            textSize(14);
            text('Score: ' + this.score, width -70, 20)
        }
    }

}

O placar armazena o estado e a cada evento importante ele é notificado. Esse é um exemplo muito simples, só que o recurso principal que vai fazer a diferença é o cálculo de colisão que está presente nas bolinhas.

Cálculos de colisão

No início não pensei em utilizar alguma fórmula matemática já existente, fiz várias implementações, só que varias não funcionaram bem, confesso que cheguei em algumas implementações que poderiam até ser utilizadas, só que o código tinha ficado um pouco extenso e complicado.

Após pesquisar sobre colisões em física, encontrei a fórmula de Colisão elástica e até existe um artigo no wikipedia sobre o tema, muito interessante, foi nele que me inspirei.

E com base nisso, após ver várias implementações, apliquei no projeto

segue abaixo a formula utilizada:

    ...

    this.colisionBall = (otherParticle) => {
        const xVelocityDiff = this.strong.x - otherParticle.strong.x;
        const yVelocityDiff = this.strong.y - otherParticle.strong.y;
    
        const xDist = otherParticle.x - this.x;
        const yDist = otherParticle.y - this.y;
    
        if (xVelocityDiff * xDist + yVelocityDiff * yDist >= 0) {
            const angle = -Math.atan2(otherParticle.y - this.y, otherParticle.x - this.x);
    
            const m1 = this.mass
            const m2 = otherParticle.mass

            const u1 = rotate(this.strong, angle);
            const u2 = rotate(otherParticle.strong, angle);

            const v1 = { x: u1.x * (m1 - m2) / (m1 + m2) + u2.x * 2 * m2 / (m1 + m2), y: u1.y };
            const v2 = { x: u2.x * (m1 - m2) / (m1 + m2) + u1.x * 2 * m2 / (m1 + m2), y: u2.y };
    
            const vFinal1 = rotate(v1, -angle);
            const vFinal2 = rotate(v2, -angle);
    
            this.strong.x = vFinal1.x;
            this.strong.y = vFinal1.y;
    
            otherParticle.strong.x = vFinal2.x;
            otherParticle.strong.y = vFinal2.y;
        }
    }
    
    ...

Uma abordagem que escolhi, foi a de que cada elemento precisa fazer o seu trabalho da melhor forma possível.

Podemos intender isso observando as implementações para as bolinhas, cada círculo é o responsável em saber sua velocidade, força e identificar a colisão.

Quando inicia o desenvolvimento, podemos imaginar que precisamos de uma rotina para gerenciar a posição de cada elemento, mais o processo fica mais simples quando cada um cuida da sua vida.

Cada uma das 16 bolinhas se autogerenciam, são responsáveis por calcular a velocidade, rodar o algorítimo de colisão elástica e verificar se esbarrou em algum outro elemento.

Após definir todos os comportamentos, vamos para o arquivo script.js e fazemos as chamadas dessas classes, instanciando os objetos na ordem o forma correta.

Na função setup você vai notar que criamos alguns dos objetos principais:

function setup() {
    createBalls()
    
    // criar mesa 
    mainTable = new Table(80, 80, 550, 320)

    // criar o taco
    stick = new Stick(300, 300)

    // criar o state para gerenciar o estado do jogo
    state = new State()

    // criar o gerenciado de pontos
    score = new Score()
}

Dentro da função createBalls, criamos todas as bolinhas de sinuca com suas configurações.

Para criar a white ball simplesmente passamos para a classe Ball suas configurações iniciais que são posições, cor, numero e um argumento booleano que indica se essa é a bolinha principal.

new Ball(250, 450, rgb(255, 255, 255), 0, true)

e a execução fica no evento draw.

Essa é uma explicação básica de como o projeto foi pensado. Todo o código está disponível, caso você tenha se interessado, pode acessar o projeto e verificar em mais detalhes.