Janeiro de 2016

Volume 31 – Número 1

Desenvolvimento de jogos – Babylon.js: recursos avançados para aprimorar seu primeiro jogo na Web

Por Raanan Weber

Na edição de dezembro, iniciei este tutorial examinando os blocos de construção básicos do Babylon.js, um mecanismo de jogo 3D baseado em WebGL (msdn.com/magazine/mt595753). Comecei projetando um jogo de boliche muito simples usando as ferramentas oferecidas pelo Babylon.js. Até o momento, o jogo inclui os objetos necessários: uma bola de boliche, a pista, as calhas e dez pinos.

Agora vou mostrar como dar vida ao jogo: como arremessar a bola, acertar os pinos, adicionar alguns efeitos de áudio, fornecer diferentes ângulos da câmera e muito mais.

Esta parte do tutorial naturalmente baseia-se no código da primeira parte e o estende. Para diferenciar as duas partes, criei um novo arquivo JavaScript para conter o código que será usado nesta parte. Mesmo ao estender a funcionalidade de determinado objeto (como a cena ou a câmera principal), não o implementarei na função que criou o objeto na primeira parte. A única função que precisarei estender é init, que contém todas as variáveis necessárias para ambas as partes do tutorial.

Isso foi feito para sua conveniência. Ao implementar seu próprio jogo, é claro, você deverá usar o paradigma de programação e a sintaxe que desejar. Recomendo que você experimente o TypeScript e seu estilo de programação orientado a objetos. É ótimo para organizar um projeto.

Durante o tempo que levei para escrever este artigo, foi lançada uma nova versão do Babylon.js. Vou continuar usando a versão 2.1. Para ver as novidades no Babylon.js 2.2, acesse bit.ly/1RC9k6e.

Detecção de colisões nativa

A câmera principal usada no jogo é a câmera livre, que permite ao jogador percorrer toda a cena em 3D usando o mouse e o teclado. No entanto, sem algumas modificações, a câmera poderia flutuar na cena, atravessar paredes ou até mesmo o piso e a pista. Para criar um jogo realista, o jogador só deveria poder se mover no chão e na pista.

Para habilitar isso, usarei o sistema de detecção de colisões interno do Babylon. O sistema de detecção de colisões impede que dois objetos sejam mesclados. Ele também tem um recurso de gravidade que impede o jogador de flutuar no ar ao andar para a frente olhando para cima.

Primeiro, vamos habilitar a detecção de colisões e a gravidade:

function enableCameraCollision(camera, scene) {
  // Enable gravity on the scene. Should be similar to earth's gravity. 
  scene.gravity = new BABYLON.Vector3(0, -0.98, 0);
  // Enable collisions globally. 
  scene.collisionsEnabled = true;
  // Enable collision detection and gravity on the free camera. 
  camera.checkCollisions = true;
  camera.applyGravity = true;
  // Set the player size, the camera's ellipsoid. 
  camera.ellipsoid = new BABYLON.Vector3(0.4, 0.8, 0.4);
}

Essa função habilita o sistema de detecção de colisões na cena e na câmera do jogo e define o elipsoide da câmera, que pode ser visto como o tamanho do jogador. Trata-se de uma caixa, com dimensões (neste caso) de 0.8 x 1.6 x 0.8 unidades, o tamanho médio aproximado de um ser humano. A câmera precisa dessa caixa, pois não é uma malha. O sistema de detecção de colisões do Babylon.js inspeciona colisões apenas entre malhas. É por isso que uma malha deve ser simulada para a câmera. O elipsoide define o centro do objeto, assim, 0.4 se traduz como um tamanho de 0.8. Também habilitei a gravidade da cena, que será aplicada ao movimento da câmera.

Depois que as colisões são habilitadas para a câmera, preciso habilitar a inspeção de colisões com o chão e com a pista. Isso é feito com um sinalizador booliano simples definido em cada malha contra a qual desejo colidir:

function enableMeshesCollision(meshes) {
  meshes.forEach(function(mesh) {
    mesh.checkCollisions = true;
  });
}

A etapa final é a adição das duas chamadas de função à função init:

// init function from the first part of the tutorial.
  ...
  enableCameraCollision(camera, scene);
  enableMeshesCollision[[floor, lane, gutters[0], gutters[1]]);
}

A única tarefa da detecção de colisões é evitar que as malhas sejam mescladas. Seu recurso de gravidade foi implementado para manter a câmera no chão. Para criar uma interação física realista entre malhas específicas (neste caso, a bola e os pinos), é necessário um sistema mais complexo: o mecanismo de física.

Como arremessar a bola — integração da física no Babylon.js

A ação principal do jogo é arremessar a bola em direção aos pinos. Os requisitos são bastante simples: O jogador deve poder estabelecer a direção e a força do arremesso. Se forem atingidos pinos específicos, eles deverão cair. Se os pinos baterem em outros pinos, estes também deverão cair. Se o jogador arremessar a bola para o lado, ela deverá cair na calha. Os pinos devem cair de acordo com a velocidade com que a bola foi arremessada contra eles.

Esse é exatamente o domínio de um mecanismo de física. O mecanismo de física calcula a dinâmica dos corpos das malhas em tempo real e calcula seu próximo movimento de acordo com as forças aplicadas. Simplificando, é o mecanismo de física que decide o que acontece com uma malha quando outra malha colide com ela ou quando as malhas são movidas por um usuário. Ele leva em conta a velocidade instantânea, o peso e a forma da malha e muito mais.

Para calcular a dinâmica do corpo rígido (o próximo movimento da malha no espaço) em tempo real, o mecanismo de física deve simplificar a malha. Para isso, cada malha tem um impostor: uma malha simples que a circunda (geralmente, uma esfera ou uma caixa). Isso reduz a precisão dos cálculos, mas possibilita um cálculo mais rápido das forças físicas que movem o objeto. Para saber mais sobre como funciona o mecanismo de física, acesse bit.ly/1S9AIsU.

O mecanismo de física não faz parte do Babylon.js. Em vez de adotar um único mecanismo, os desenvolvedores da estrutura decidiram implementar interfaces para diferentes mecanismos de física e permitir que os desenvolvedores decidam qual deles usar. Atualmente, o Babylon.js tem interfaces para dois mecanismos de física: Cannon.js (cannonjs.org) e Oimo.js (github.com/lo-th/Oimo.js). Ambos são maravilhosos! Pessoalmente, acho a integração com o Oimo um pouco melhor. Portanto, vou usá-lo em meu jogo de boliche. A interface para o Cannon.js foi completamente reescrita para o Babylon.js 2.3, que agora está em alfa, e agora é compatível com a última versão do Cannon.js com muitas correções de bugs e novos impostores,incluindo mapas de altura complexos. Recomendo que você o experimente se usar o Babylon.js 2.3 ou posterior.

O mecanismo de física é habilitado com uma linha de código simples:

scene.enablePhysics(new BABYLON.Vector3(0, -9.8, 0), new BABYLON.OimoJSPlugin());

Isso define a gravidade da cena e o mecanismo de física a ser usado. Para substituir o Oimo.js pelo Cannon.js, basta mudar a segunda variável para:

new BABYLON.CannonJSPlugin()

Em seguida, preciso definir os impostores em todos os objetos. Isso é feito com a função setPhysicsState da malha. Por exemplo, aqui está a definição da pista:

lane.setPhysicsState(BABYLON.PhysicsEngine.BoxImpostor, {
  mass: 0,
  friction: 0.5,
  restitution: 0
});

A primeira variável é o tipo de impostor. Como a pista é uma caixa perfeita, uso o impostor Caixa. A segunda variável é a definição física do corpo: seu peso (em quilogramas) e seu fator de atrito e de elasticidade. A massa da pista é 0, pois quero que ela permaneça na sua localização. A definição da massa como 0 em um impostor mantém o objeto bloqueado em sua posição atual.

Para a esfera, vou usar o impostor Esfera. Uma bola de boliche pesa cerca de 6.8 kg e geralmente é muito lisa, portanto o atrito não é necessário:

ball.setPhysicsState(BABYLON.PhysicsEngine.SphereImpostor, {
  mass: 6.8,
  friction: 0,
  restitution: 0
});

Se você quer saber por que uso quilogramas em vez de libras, é pelo mesmo motivo pelo qual uso metros em vez de pés em todo o projeto: o mecanismo de física usa o sistema métrico. Por exemplo, a definição de gravidade padrão é (0, -9.8, 0), que é aproximadamente a gravidade da Terra. As unidades usadas são metros por segundo ao quadrado (m/s2).

Agora preciso poder arremessar a bola. Para isso, usarei outro recurso do mecanismo de física: a aplicação de um impulso a um determinado objeto. Aqui, o impulso é uma força em uma direção específica que é aplicada a malhas com a física habilitada. Por exemplo, para arremessar a bola para a frente, usarei o seguinte:

ball.applyImpulse(new BABYLON.Vector3(0, 0, 20), ball.getAbsolutePosition());

A primeira variável é o vetor do impulso. Aqui, são 20 unidades no eixo Z, que é para a frente quando a cena é redefinida. A segunda variável especifica onde a força deve ser aplicada no objeto. Nesse caso, é o centro da bola. Pense em uma jogada com efeito no jogo de sinuca: o taco pode bater na bola em muitos pontos diferentes, não só no centro. É assim que você pode simular esse comportamento.

Agora posso arremessar a bola para a frente. A Figura 1 mostra o que é exibido quando a bola bate nos pinos.

A bola de boliche batendo nos pinos
Figura 1 A bola de boliche batendo nos pinos

No entanto, ainda faltam a direção e a força.

Há muitas maneiras de definir a direção. As duas melhores opções são usar a direção atual da câmera ou a posição de toque do ponteiro. Usarei a segunda opção.

Para encontrar o ponto que o usuário tocou no espaço, é usado o objeto PickingInfo, enviado por cada evento de ponteiro para baixo e para cima. O objeto PickingInfo contém informações sobre o ponto em que o evento foi disparado, incluindo a malha que foi tocada, o ponto da malha que foi tocado, a distância até esse ponto e outras informações. Se nenhuma malha tiver sido tocada, a variável de ocorrência do PickingInfo será falsa. Uma cena do Babylon.js tem duas funções de retorno de chamada úteis que me ajudarão a obter as informações de seleção: onPointerUp e onPointerDown. Esses dois retornos de chamada são disparados quando eventos de ponteiro são disparados, e sua assinatura é a seguinte:

function(evt: PointerEvent, pickInfo: PickingInfo) => void

A variável evt é o evento JavaScript original que foi disparado. A segunda variável consiste nas informações de seleção geradas pela estrutura em cada evento disparado.

Posso usar esses retornos de chamada para arremessar a bola nessa direção:

scene.onPointerUp = function(evt, pickInfo) {
  if (pickInfo.hit) {
    // Calculate the direction using the picked point and the ball's position. 
    var direction = pickInfo.pickedPoint.subtract(ball.position);
    // To be able to apply scaling correctly, normalization is required.
    direction = direction.normalize();
    // Give it a bit more power (scale the normalized direction).
    var impulse = direction.scale(20);
    // Apply the impulse (and throw the ball). 
    ball.applyImpulse(impulse, new BABYLON.Vector3(0, 0, 0));
  }
}

Agora posso arremessar a bola em uma direção específica. Só falta a força do arremesso. Para adicioná-la, calcularei o delta dos quadros entre os eventos de ponteiro para cima e para baixo. A Figura 2 mostra a função usada para arremessar a bola com uma força específica.

Figura 2 Arremesso da bola com força e direção

var strengthCounter = 5;
var counterUp = function() {
  strengthCounter += 0.5;
}
// This function will be called on pointer-down events.
scene.onPointerDown = function(evt, pickInfo) {
  // Start increasing the strength counter. 
  scene.registerBeforeRender(counterUp);
}
// This function will be called on pointer-up events.
scene.onPointerUp = function(evt, pickInfo) {
  // Stop increasing the strength counter. 
  scene.unregisterBeforeRender(counterUp);
  // Calculate throw direction. 
  var direction = pickInfo.pickedPoint.subtract(ball.position).normalize();
  // Impulse is multiplied with the strength counter with max value of 25.
  var impulse = direction.scale(Math.min(strengthCounter, 25));
  // Apply the impulse.
  ball.applyImpulse(impulse, ball.getAbsolutePosition());
  // Register a function that will run before each render call 
  scene.registerBeforeRender(function ballCheck() {
    if (ball.intersectsMesh(floor, false)) {
      // The ball intersects with the floor, stop checking its position.  
      scene.unregisterBeforeRender(ballCheck);
      // Let the ball roll around for 1.5 seconds before resetting it. 
      setTimeout(function() {
        var newPosition = scene.activeCameras[0].position.clone();
        newPosition.y /= 2;
        resetBall(ball, newPosition);
      }, 1500);
    }
  });
  strengthCounter = 5;
}

Uma observação sobre eventos de ponteiro: deliberadamente, não usei o termo "clique". O Babylon.js usa o sistema de Eventos de Ponteiro, que estende os recursos de clique do mouse do navegador para o toque e outros dispositivos de entrada capazes de clicar, tocar e apontar. Assim, um toque em um smartphone ou um clique do mouse em um computador disparam o mesmo evento. Para simular isso em navegadores que não têm esse recurso, o Babylon.js usa o hand.js, um polyfill para eventos de ponteiro que também está incluído no index.html do jogo. Você pode ler mais sobre o hand.js em sua página no GitHub em bit.ly/1S4taHF. O rascunho de Eventos de Ponteiro pode ser encontrado em bit.ly/1PAdo9J. Observe que o hand.js será substituído em versões futuras pelo jQuery PEP (bit.ly/1NDMyYa).

Isso é tudo quanto à física! O jogo de boliche ficou muito melhor.

Como adicionar efeitos de áudio

A adição de efeitos de áudio a um jogo melhora muito a experiência do usuário. O áudio pode criar a atmosfera certa e dar um pouco mais de realismo ao jogo de boliche. Felizmente, o Babylon.js inclui um mecanismo de áudio, introduzido na versão 2.0. O mecanismo de áudio se baseia na API de Áudio da Web (bit.ly/1YgBWWQ), que tem suporte em todos os principais navegadores, exceto o Internet Explorer. Os formatos de arquivo que podem ser usados dependem do próprio navegador.

Adicionarei três efeitos de áudio diferentes. O primeiro é o som ambiente, o som que simula o ambiente. No caso de um jogo de boliche, os sons de uma casa de boliche normalmente funcionariam muito bem. Porém, como criei a pista de boliche ao ar livre, na grama, alguns sons da natureza serão melhores.

Para adicionar o som ambiente, vou carregar o som, reproduzi-lo automaticamente e colocá-lo em um loop constante:

var atmosphere = new BABYLON.Sound("Ambient", "ambient.mp3", scene, null, {
  loop: true,
  autoplay: true
});

O som será reproduzido continuamente a partir do momento em que for carregado.

O segundo efeito de áudio que vou adicionar é o som de a bola rolando na pista de boliche. O som será reproduzido enquanto a bola estiver na pista, mas, assim que ela sair da pista, ele será interrompido.

Primeiro, vou criar o som de rolamento:

var rollingSound = new BABYLON.Sound("rolling", "rolling.mp3", scene, null, {
  loop: true,
  autoplay: false
});

O som será carregado, mas não será reproduzido até que eu execute sua função de reprodução, o que ocorrerá quando a bola for arremessada. Estendi a função da Figura 2 e adicionei o seguinte:

...ball.applyImpulse(impulse, new BABYLON.Vector3(0, 0, 0));
// Start the sound.
rollingSound.play();
...

Interrompo o som quando a bola sai da pista:

...
If(ball.intersectsMesh(floor, false)) {
  // Stop the sound.
  rollingSound.stop();
  ...
}
...

O Babylon.js permite anexar um som a uma malha específica. Assim, ele calcula automaticamente o volume e o movimento panorâmico do som usando a posição da malha e cria uma experiência mais realista. Para fazer isso, simplesmente adiciono a seguinte linha após criar o som:

rollingSound.attachToMesh(ball);

Agora o som sempre será reproduzido com base na posição da bola.

O último efeito sonoro que desejo adicionar é a bola batendo nos pinos. Para fazer isso, criarei o som e o anexarei ao primeiro pino:

var hitSound = new BABYLON.Sound("hit", "hit.mp3", scene);
hitSound.attachToMesh(pins[0]);

Esse som não será repetido em loop e não será reproduzido automaticamente.

Reproduzirei esse som sempre que a bola bater em um dos pinos. Para que isso aconteça, adicionarei uma função que, após a bola ser arremessada, inspecionará constantemente se a bola atinge algum pino. Se ela o atingir, cancelarei o registro da função e reproduzirei o som. Para isso, adiciono as seguintes linhas à função scene.onPointerUp da Figura 2:

scene.registerBeforeRender(function ballIntersectsPins() {
  // Some will return true if the ball hit any of the pins.
  var intersects = pins.some(function (pin) {
    return ball.intersectsMesh(pin, false);
  });
  if (intersects) {
    // Unregister this function – stop inspecting the intersections.
    scene.unregisterBeforeRender(ballIntersectsPins);
    // Play the hit sound.
    hit.play();
  }
});

Agora o jogo tem todos os efeitos de áudio que eu queria adicionar. Em seguida, continuarei a melhorar o jogo adicionando um placar.

Observe que não posso incluir os efeitos de áudio que usei com o projeto associado devido aos direitos autorais do material. Não encontrei amostras de áudio livremente disponíveis que eu também pudesse publicar. Portanto, o código está marcado como comentário. Ele funcionará se você adicionar as três amostras de áudio que usei.

Como adicionar um placar

Depois que um jogador acerta os pinos, seria ótimo ver realmente quantos deles ainda estão de pé e quantos caíram. Para isso, adicionarei um placar.

O placar em si será um plano preto simples com texto branco. Para criá-lo, usarei o recurso de textura dinâmica. Basicamente, trata-se de uma tela em 2D que pode ser usada como textura para objetos em 3D no jogo.

A criação do plano e da textura dinâmica é simples:

var scoreTexture = new BABYLON.DynamicTexture("scoreTexture", 512, scene, true);
var scoreboard = BABYLON.Mesh.CreatePlane("scoreboard", 5, scene);
// Position the scoreboard after the lane.
scoreboard.position.z = 40;
// Create a material for the scoreboard.
scoreboard.material = new BABYLON.StandardMaterial("scoradboardMat", scene);
// Set the diffuse texture to be the dynamic texture.
scoreboard.material.diffuseTexture = scoreTexture;

A textura dinâmica permite desenhar diretamente na tela subjacente usando sua função getContext, que retorna um CanvasRenderingContext2D (mzl.la/1M2mz01). O objeto de textura dinâmica também fornece algumas funções de ajuda que podem ser úteis se não quero lidar diretamente com o contexto da tela. Uma das funções é drawText, que permite desenhar uma cadeia de caracteres usando uma fonte específica sobre a tela. Atualizarei a tela sempre que o número de pinos caídos mudar:

var score = 0;
scene.registerBeforeRender(function() {
  var newScore = 10 - checkPins(pins, lane);
  if (newScore != score) {
    score = newScore;
    // Clear the canvas. 
    scoreTexture.clear();
    // Draw the text using a white font on black background.
    scoreTexture.drawText(score + " pins down", 40, 100,
      "bold 72px Arial", "white", "black");
  }
});

É fácil verificar se os pinos caíram: verifico apenas se sua posição no eixo Y é igual à posição Y original de todos os pinos (a variável predefinida 'pinYPosition'):

function checkPins(pins) {
  var pinsStanding = 0;
  pins.forEach(function(pin, idx) {
    // Is the pin still standing on top of the lane?
    if (BABYLON.Tools.WithinEpsilon(pinYPosition, pin.position.y, 0.01)) {
      pinsStanding++;
    }
  });
  return pinsStanding;
}

A textura dinâmica pode ser vista na Figura 3.

O placar do jogo
Figura 3 O placar do jogo

Agora falta apenas uma função para redefinir a pista e o placar. Adicionarei um gatilho de ação que funcionará quando a tecla R for pressionada no teclado (veja a Figura 4).

Figura 4 Redefinição da pista e do placar

function clear() {
  // Reset the score.
  score = 0;
  // Initialize the pins.
  initPins(scene, pins);
  // Clear the dynamic texture and draw a welcome string.
  scoreTexture.clear();
  scoreTexture.drawText("welcome!", 120, 100, "bold 72px Arial", "white", "black");
}
scene.actionManager.registerAction(new BABYLON.ExecuteCodeAction({
  trigger: BABYLON.ActionManager.OnKeyUpTrigger,
  parameter: "r"
}, clear));

O pressionamento da tecla R redefinirá/inicializará a cena.

Adição de uma câmera para seguimento

Um belo efeito que desejo adicionar ao jogo é uma câmera que seguirá a bola de boliche quando ela for arremessada. Quero uma câmera que "role" junto com a bola em direção aos pinos e pare quando a bola voltar à posição original. Posso fazer isso usando o recurso de multiexibição do Babylon.js.

Na primeira parte deste tutorial, defini o objeto câmera livre como a câmera ativa da cena usando:

scene.activeCamera = camera

A variável câmera ativa informa à cena qual câmera deve ser renderizada, caso tenha sido definida mais de uma. Isso é adequado quando quero usar uma única câmera durante todo o jogo. Porém, se eu quiser um efeito de "imagem na imagem", uma câmera ativa não será suficiente. Em vez disso, preciso usar a matriz de câmeras ativas armazenada na cena com o nome de variável scene.activeCameras. As câmeras nessa matriz serão renderizadas uma após a outra. Se scene.activeCameras não estiver vazia, scene.activeCamera será ignorada.

A primeira etapa é adicionar a câmera livre original à matriz. Isso é feito facilmente na função init. Substitua scene.activeCamera = camera por:

scene.activeCameras.push(camera);

Na segunda etapa, criarei uma câmera para seguimento quando a bola for arremessada:

var followCamera = new BABYLON.FollowCamera("followCamera", ball.position, scene);
followCamera.radius = 1.5; // How far from the object should the camera be.
followCamera.heightOffset = 0.8; // How high above the object should it be.
followCamera.rotationOffset = 180; // The camera's angle. here - from behind.
followCamera.cameraAcceleration = 0.5 // Acceleration of the camera.
followCamera.maxCameraSpeed = 20; // The camera's max speed.

Isso cria a câmera e a configura para ficar 1.5 unidades atrás e 0.8 unidades acima do objeto que ela está prestes a seguir. O objeto seguido deve ser a bola, mas há um problema: a bola pode girar, e a câmera girará com ela. O que eu quero obter é uma "rota de voo" por trás do objeto. Para isso, criarei um objeto de seguimento que terá a posição da bola, mas não sua rotação:

// Create a very small simple mesh.
var followObject = BABYLON.Mesh.CreateBox("followObject", 0.001, scene);
// Set its position to be the same as the ball's position.
followObject.position = ball.position;

Em seguida, definirei o alvo da câmera como sendo o followObject:

followCamera.target = followObject;

Agora, a câmera seguirá o objeto de seguimento que está se movendo junto com a bola.

A última configuração necessária para a câmera é o visor. Cada câmera pode definir o espaço na tela que usará. Isso é feito com a variável de visor, que é definida com as seguintes variáveis:

var viewport = new BABYLON.Viewport(xPosition, yPosition, width, height);

Todos os valores estão entre 0 e 1, como uma porcentagem (em que 1 corresponde a 100%) em relação à altura e à largura da tela. Os dois primeiros valores definem o ponto de partida do retângulo da câmera na tela, e a largura e a altura definem a largura e a altura do retângulo em relação ao tamanho real da tela. As configurações padrão do visor são (0.0, 0.0, 1.0, 1.0), o que abrange toda a tela. Para a câmera de seguimento, definirei a altura e a largura como 30% da tela:

followCamera.viewport = new BABYLON.Viewport(0.0, 0.0, 0.3, 0.3);

A Figura 5 mostra a exibição do jogo depois que uma bola é arremessada. Observe a exibição no canto inferior esquerdo.

Efeito de imagem na imagem com a câmera de seguimento
Figura 5 Efeito de imagem na imagem com a câmera de seguimento

O controle de entrada (qual tela reage aos eventos de entrada, como eventos do ponteiro ou do teclado) ficará com a câmera livre definida na primeira parte deste tutorial. Isso já foi definido na função init.

Como desenvolver mais o jogo

No início, mencionei que esse jogo seria um protótipo. Há mais alguns itens a serem implementados para torná-lo um jogo real.

O primeiro seria adicionar uma GUI, que pede o nome do usuário e, por exemplo, mostra opções de configuração (Talvez possamos jogar boliche em Marte! Basta definir a gravidade de forma diferente.) e qualquer outro item de que o usuário precise.

O Babylon.js não oferece uma maneira nativa de criar uma GUI, mas os membros da comunidade criaram algumas extensões que você pode usar para criar GUIs maravilhosas, incluindo CastorGUI (bit.ly/1M2xEhD), bGUi (bit.ly/1LCR6jk) e a extensão de diálogo em bit.ly/1MUKpXH. A primeira usa HTML e CSS para adicionar camadas sobre a tela em 3D. As outras adicionam diálogos em 3D à própria cena usando malhas regulares e texturas dinâmicas. Recomendo que você as experimente antes de escrever sua própria solução. Elas são fáceis de usar e simplificarão muito o processo de criação de GUI.

Outro aprimoramento seria o uso de malhas melhores. As malhas do meu jogo foram todas criadas usando funções internas do Babylon.js, e, claramente, há um limite para o que se pode fazer apenas com código. Há muitos sites que oferecem objetos em 3D gratuitos e pagos. O melhor, na minha opinião, é o TurboSquid (bit.ly/1jSrTZy). Procure malhas low poly para obter um melhor desempenho.

Após a adição de malhas melhores, por que não adicionar um objeto humano que arremessará realmente a bola? Para isso, você precisará do recurso de animação de ossos integrado do Babylon.js e de uma malha que dê suporte ao recurso. Você encontrará uma demonstração de sua aparência em babylonjs.com/BONES.

Como toque final, tente tornar o jogo compatível com a realidade virtual. Nesse caso, a única modificação necessária é a câmera a ser usada. Substitua a FreeCamera por uma WebVRFreeCamera e veja como é fácil direcionar o Google Cardboard.

Há muitas outras melhorias que você pode fazer: adicionar uma câmera acima dos pinos, adicionar mais pistas ao lado para a funcionalidade de vários jogadores, limitar o movimento da câmera e a posição de onde a bola pode ser arremessada e muito mais. Deixarei que você descubra alguns desses outros recursos por conta própria.

Conclusão

Espero que você tenha se divertido tanto ao ler este tutorial quanto eu me diverti ao escrevê-lo, que ele o oriente na direção certa e o incentive a experimentar o Babylon.js. Essa é realmente uma estrutura maravilhosa, criada com carinho por desenvolvedores para desenvolvedores. Acesse babylonjs.com para obter mais demonstrações da estrutura. E, novamente, não hesite em ingressar no fórum de suporte para tirar suas dúvidas. Não existem perguntas bobas, apenas respostas bobas!


Raanan Weberé consultor de TI, desenvolvedor de pilha completo, marido e pai. Em seu tempo livre, ele contribui para o Babylon.js e outros projetos de software livre. Você pode ler seu blog em blog.raananweber.com.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: David Catuhe