Enero de 2016

Volumen 31, número 1

Desarrollo de juegos: Babylon.js: Mejorar su primer juego en la web con características avanzadas

Por Raanan Weber

En la edición de diciembre, empecé este tutorial con una revisión de los conceptos básicos de Babylon.js, un motor de juego 3D basado en WebG (msdn.com/magazine/mt595753). Comencé a diseñar un juego de bolos muy sencillo con las herramientas que ofrece Babylon.js. Hasta ahora, el juego incluye los objetos necesarios: una bola, la pista, las canaletas y los 10 bolos.

Ahora, le mostraré cómo hacer que el juego cobre vida; cómo lanzar la bola, darle a los bolos, agregar algunos efectos de audio, proporcionar distintas vistas de la cámara y muchos más.

Esta parte del tutorial depende, naturalmente, del código de la primera parte y lo amplía. Para distinguir entre las dos partes, creé un nuevo archivo JavaScript que contendrá el código que se usará en esta parte. Incluso cuando se amplíe la funcionalidad de un objeto determinado (por ejemplo, una escena o la cámara principal), no la implementaré en la función que creó el objeto en la primera parte. La única función que tengo que ampliar es init, que contiene todas las variables que se necesitan para ambas partes del tutorial.

Esto se hace para su comodidad. Cuando implemente su propio juego, debería, evidentemente, usar el paradigma y la sintaxis de programación que quiera. Recomiendo probar TypeScript y su estilo de programación orientado a los objetos. Es genial para la organización de un proyecto.

En el tiempo que me llevó escribir este artículo, se lanzó una nueva versión de Babylon.js. Seguiré usando la versión 2.1. Para conocer las novedades de Babylon.js 2.2, vaya a bit.ly/1RC9k6e.

Detección de colisiones nativa

La cámara principal que se usa en el juego es una cámara libre, que permite al jugador desplazarse por toda la escena en 3D con el mouse y el teclado. Sin embargo, sin determinadas modificaciones, la cámara podría flotar por la escena o pasar por muros, o incluso por el suelo o la pista. Para crear un juego realista, el jugador debería poder moverse solo en el suelo y la pista.

Para ello, usaré el sistema de detección de colisiones interno de Babylon. El sistema de colisión impide que dos objetos se fusionen entre sí. También cuenta con una característica de gravedad que impide al jugador flotar en el aire cuando camine hacia delante mientras mira hacia delante.

En primer lugar, habilitemos la detección de colisiones y la gravedad:

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);
}

Esta función habilita el sistema colisiones en la escena y la cámara del juego, y establece el elipsoide de la cámara, que se puede ver como el tamaño del jugador. Se trata de un cuadro, con un tamaño (en este caso) de 0,8 x 1,6 x 0,8 unidades, el tamaño medio, más o menos, de una persona. La cámara necesita este cuadro porque no es una malla. El sistema de colisiones de Babylon.js inspecciona las colisiones solo entre las mallas y es por eso que se debe simular una malla para la cámara. El elipsoide define el centro del objeto, por lo que 0,4 se traduce en 0,8 en tamaño. También habilité la gravedad de la escena, que se puede aplicar al movimiento de la cámara.

Una vez que se habiliten las colisiones de la cámara, tengo que habilitar la inspección de colisiones del suelo y la pista. Para ello, se establece una sencilla marca booleana en cada malla contra la que quiero colisionar:

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

El último paso es la adición de dos llamadas de función a la función init:

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

La única tarea de la detección de colisión es impedir que las mallas se fusionen entre sí. Su característica de gravedad se implementó para mantener la cámara en el suelo. Para crear una interacción física realista entre mallas específicas (en este caso, la bola y los bolos) se necesita un sistema más completo: el motor de leyes físicas.

Lanzar la bola, integración de leyes físicas en Babylon.js

La principal acción del juego es lanzar la bola hacia los bolos. Los requisitos son relativamente simples: el jugador debe poder establecer la dirección y la fuerza del lanzamiento. Si se tocan bolos específicos, estos se deben caer. Si los bolos tocan otros bolos, estos también se deberían caer. Si el jugador lanza la bola hacia un lado, esta debería caer en la canaleta. Los bolos deben caerse según la velocidad con la que la bola se lanza hacia ellos.

Esto es exactamente el dominio del motor de leyes físicas. El motor de leyes físicas calcula la dinámica del cuerpo de las mallas en tiempo real y su movimiento subsiguiente según las fuerzas aplicadas. Sencillamente, el motor de leyes físicas es el que decide qué le sucede a una malla cuando otra malla colisiona con ella o cuando el usuario mueve las mallas. Tiene en cuenta la velocidad, peso, forma, etc. actuales de la malla.

Para calcular la dinámica de cuerpo rígido (el siguiente movimiento de la malla en el espacio) en tiempo real, el motor de leyes físicas debe simplificar la malla. Para ello, cada malla tiene un impostor; una malla sencilla que la limita (normalmente una esfera o cuadro). Esto reduce la precisión de los cálculos, pero permite un cálculo más rápido de las fuerzas físicas que mueven el objeto. Para más información sobre el funcionamiento del motor de leyes físicas, consulte bit.ly/1S9AIsU.

El motor de leyes físicas no forma parte de Babylon.js. En lugar de comprometerse con un único motor, los desarrolladores del marco decidieron implementar interfaces a distintos motores de leyes físicas y dejar que los desarrolladores decidan cuál quieran usar. Actualmente, Babylon.js tiene interfaces a dos motores de leyes físicas: Cannon.js (cannonjs.org) y Oimo.js (github.com/lo-th/Oimo.js). Ambos son de gran utilidad. Personalmente, creo que Oimo ofrece mejor integración, por lo que lo usaré en mi juego de bolos. La interfaz de Cannon.js se rediseñó completamente para Babylon.js 2.3, que está en su versión alfa. Ahora, se admite la versión más reciente de Cannon.js, con varias correcciones de errores y nuevos impostores, como por ejemplo, asignaciones de altura complejas. Recomienda que la pruebe si usa Babylon.js 2.3 o una versión posterior.

El motor de leyes físicas se habilita con una línea de código sencilla:

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

Esto establece la gravedad de la escena y define el motor de leyes físicas que se va a usar. El reemplazo de Oimo.js por Cannon.js tan solo requiere cambiar la segunda variable a:

new BABYLON.CannonJSPlugin()

A continuación, tengo que definir los impostores en todos los objetos. Para ello, se usa la función setPhysicsState de la malla. Por ejemplo, esta es la definición de la pista:

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

La primera variable corresponde al tipo de impostor. Dado que la pista es un cuadro perfecto, uso el impostor Box. La segunda variable corresponde a la definición de leyes físicas del cuerpo; su peso (en kg), fricción y factor de restitución. La masa de la pista es 0, ya que quiero que permanezca en su ubicación. Al establecer la masa en 0 en un impostor, el objeto permanece bloqueado en su posición actual.

Para la esfera, usaré el impostor Sphere. Una bola pesa unos 6,8 kg y suele ser muy lisa, por lo que no se necesita ninguna fricción:

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

Si se pregunta por qué uso kilogramos en lugar de libras, es por el mismo motivo que uso metros en lugar de pies en todo el proyecto: el motor de leyes físicas usa el sistema métrico. Por ejemplo, la definición predeterminada de la gravedad es (0, -9,8, 0), que es la gravedad aproximada de la Tierra. Las unidades que se usan son metros por segundo cuadrado (m/s2).

Ahora, tengo que poder lanzar la bola. Para ello, uso una característica diferente del motor de leyes físicas, aplicando un impulso a un objeto determinado. Aquí, el impulso es una fuerza en una dirección concreta que se aplica a las mallas con leyes físicas habilitadas. Por ejemplo, para lanzar la bola hacia delante, usaré lo siguiente:

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

La primera variable corresponde al vector del impulso, aquí 20 unidades en el eje Z, que es hacia delante cuando la escena se restablece. La segunda variable especifica dónde se debe aplicar la fuerza en el objeto. En este caso, es en el centro de la bola. Piense en un tiro de fantasía en el billar, el taco puede golpear la bola en muchos puntos diferentes, no solo en el centro. Así es como puedo simular este tipo de comportamiento.

Ahora puedo lanzar la bola hacia delante. En la Figura 1 se muestra el aspecto cuando la bola golpea los bolos.

Bola que golpea los bolos
Figura 1 Bola que golpea los bolos

Sin embargo, aún me falta la dirección y la fuerza.

Hay varias maneras de establecer la dirección. Las dos mejores opciones son usar la dirección actual de la cámara o la posición de golpe del puntero. Optaré por la segunda opción.

La búsqueda del punto en el que el usuario tocó en el espacio se hace con el objeto PickingInfo, enviado por cada evento de puntero hacia abajo y puntero hacia arriba. El objeto PickingInfo contiene información sobre el punto en el que se desencadenó el evento, por ejemplo, la malla que se tocó, el punto en la malla que se tocó, la distancia a este punto, etc. Si no se tocó ninguna malla, la variable de golpe del objeto PickingInfo será false. Una escena de Babylon.js tiene dos funciones útiles de devolución de llamada que me ayudarán a obtener la información de selección: onPointerUp y onPointerDown. Esas dos devoluciones de llamada se desencadenan cuando se desencadenan eventos de puntero y su firma es la siguiente:

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

La variable evt es el evento JavaScript original desencadenado. La segunda variable es la información de selección que el marco genera en cada evento desencadenado.

Puedo usar esas devoluciones de llamada para lanzar la bola en esa dirección:

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));
  }
}

Ahora puedo lanzar la bola en una dirección concreta. Lo único que falta es la fuerza del lanzamiento. Para agregarlo, voy a calcular el valor delta de los marcos entre los eventos de puntero hacia abajo y puntero hacia arriba. En la Figura 2 se muestra la función que se usa para lanzar la bola con una fuerza específica.

Figura 2 Lanzar la bola con fuerza y dirección

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;
}

Una nota sobre los eventos de puntero; el hecho de no usar el término “clic” es intencional. Babylon.js usa el sistema de eventos de puntero, que amplía las características de clic del mouse del explorador para tocar y otros dispositivos de entrada capaces de hacer clic, tocar y apuntar. De este modo, un toque en un smartphone o un clic del mouse en un equipo de escritorio desencadenan el mismo evento. Para simular esto en exploradores que no admiten la característica, Babylon.js usa hand.js, un código polyfill para eventos de puntero que también se incluye en el archivo index.html del juego. Puede leer más sobre hand.js en su página GitHub en bit.ly/1S4taHF. El borrador de eventos de puntero se encuentra en bit.ly/1PAdo9J. Tenga en cuenta que hand.js se reemplazará en versiones futuras por jQuery PEP (bit.ly/1NDMyYa).

¡Eso es todo sobre las leyes físicas! El juego de bolos ya está mucho mejor.

Agregar efectos de audio

La adición de efectos de audio a un juego proporciona un enorme impulso a la experiencia del usuario. El audio puede crear el ambiente adecuado y agregar un poco más de realismo al juego de bolos. Por suerte, Babylon.js incluye un motor de audio, que se presentó en la versión 2.0. El motor de audio se basa en la API de audio web (bit.ly/1YgBWWQ), que se admite en todos los exploradores principales, excepto Internet Explorer. Los formatos de archivo que se pueden usar dependen del explorador en sí.

Agregaré tres efectos de audio diferentes. El primero es el sonido de ambiente, que simula el entorno. En el caso de un juego de bolos, los sonidos de una sala de bolos normalmente funcionarían bien, pero debido a que creé la pista en el exterior, sobre césped, sería mejor usar algunos sonidos de la naturaleza.

Para agregar sonido de ambiente, cargaré el sonido, lo reproduciré automáticamente y haré un bucle para que se escuche continuamente:

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

Este sonido se reproducirá continuamente desde el momento en que se cargue.

El segundo efecto de audio que agregaré es la bola que rueda por la pista. Este sonido se reproducirá siempre que la bola se encuentre en la pista, pero en el momento que la abandone, se detendrá.

En primer lugar, crearé el sonido de rodar:

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

El sonido se cargará, pero no se reproducirá hasta que ejecute la función de reproducción, que se hará cuando se lance la bola. Amplié la función de la Figura 2 y agregué lo siguiente:

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

Detengo el sonido cuando la bola abandona la pista:

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

Babylon.js me permite adjuntar un sonido a una malla concreta. De este modo, calcula automáticamente el volumen y la panorámica del sonido según la posición de la malla y crea una experiencia más realista. Para ello, simplemente agrego la siguiente línea después de crear el sonido:

rollingSound.attachToMesh(ball);

Ahora, el sonido se reproducirá siempre desde la posición de la bola.

El último efecto de sonido que quiero agregar es de la bola golpeando los bolos. Para ello, creo un sonido y lo adjunto al primer bolo:

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

Este sonido no se reproducirá en bucle ni automáticamente.

Se reproducirá cada vez que la bola golpee uno de los bolos. Para ello, agregaré una función que, tras el lanzamiento de la bola, inspeccionará continuamente si la bola interactúa con un bolo. Si forma una intersección, anulo el registro de la función y reproduzco el sonido. Para ello, agrego las siguientes líneas a la función scene.onPointerUp de la 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();
  }
});

El juego ahora tiene todos los efectos de audio que quería agregar. A continuación, sigo mejorando el juego con la adición de un marcador.

Observe que no puedo incluir los efectos de audio que usé con el proyecto que acompaña debido a los derechos de propiedad intelectual del material. No encontré muestras de audio disponibles de forma gratuita que también pudiera publicar. Por lo tanto, el código se convirtió en comentario. Funcionará si agrega las tres muestras de audio que usé.

Agregar una pantalla de puntuación

Después de que el jugador golpea los bolos, sería bueno ver realmente cuántos de ellos siguen de pie y cuántos se cayeron. Para ello, agregaré un marcador.

El marcador en sí será un panel negro sencillo con texto blanco. Para crearlo, usaré la característica de textura dinámica, que es básicamente un lienzo en 2D que se puede usar como textura para los objetos 3D del juego.

La creación del plano y la textura dinámica es sencilla:

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;

La textura dinámica me permite dibujar directamente el lienzo subyacente con su función getContext, que devuelve un objeto CanvasRenderingContext2D (mzl.la/1M2mz01). Además, el objeto de textura dinámica proporciona algunas funciones de ayuda que pueden ser de utilidad si no quiero tratar directamente el contexto del lienzo. Una de estas funciones es drawText, que me permite dibujar una cadena con una fuente concreta por encima de este lienzo. Actualizaré el lienzo cada vez que cambie el número de bolos caídos:

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");
  }
});

La comprobación de si los bolos se cayeron es trivial. Compruebo solamente si su posición en el eje Y es igual a la posición Y original de todos los bolos (la variable 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;
}

La textura dinámica se puede ver en la Figura 3.

Marcador del juego
Figura 3 Marcador del juego

Lo único que falta ahora es una función para restablecer la pista y el marcador. Agregaré un desencadenador de acción que funcionará cuando se presione la tecla R del teclado (consulte la Figura 4).

Figura 4 Restablecer la pista y el marcador

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));

Cuando se presione la tecla R, se restablecerá o inicializará la escena.

Agregar una cámara de seguimiento

Un efecto bonito que quiero agregar al juego es una cámara que seguirá la bola cuando se lance. Quiero una cámara que “ruede” junto con la bola hacia los bolos y se detenga cuando la bola vuelva a su posición original. Para ello, uso la característica de varias vistas de Babylon.js.

En la primera parte de este tutorial, establecí el objeto de cámara libre como la cámara activa de la escena con el siguiente código:

scene.activeCamera = camera

La variable de cámara activa indica a la escena qué cámara se debe representar, en el caso de que haya más de una definida. Esto está bien si quiero usar una sola cámara a lo largo de todo el juego. Sin embargo, si quiero un efecto tipo “imagen en imagen”, una cámara no es suficiente. En cambio, tengo que usar la matriz de cámaras activas almacenada en la escena bajo el nombre de variable scene.activeCameras. Las cámaras de esta matriz se representarán una después de otra. Si el objeto scene.activeCameras no está vacío, scene.activeCamera no se tendrá en cuenta.

El primer paso es agregar la cámara libre original a esta matriz. Esto se hace fácilmente en la función init. Reemplace scene.activeCamera = camera por:

scene.activeCameras.push(camera);

En el segundo paso, crearé una cámara de seguimiento cuando se lance la bola:

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.

Esto crea la cámara y la configura para que se coloque 1,5 unidades detrás y 0,8 unidades por encima del objeto que va a seguir. El objeto seguido debería ser la bola, pero hay un problema, la bola puede girar y la cámara girará con ella. Lo que necesito es una “trayectoria de vuelo” detrás del objeto. Para ello, crearé un objeto de seguimiento que obtendrá la posición de la bola, pero no su rotación:

// 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;

Luego, establezco el objetivo de la cámara en followObject:

followCamera.target = followObject;

Ahora, la cámara seguirá el objeto que se mueve junto con la bola.

La última configuración que necesita la cámara es su ventanilla. Cada cámara puede definir el espacio en pantalla que usará. Esto se consigue mediante la variable de ventanilla, que se define con las siguientes variables:

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

Todos los valores están entre 0 y 1, igual que un porcentaje (con 1 que representa 100 %) relativo a la altura y el ancho de la pantalla. Los primeros dos valores definen el punto inicial del rectángulo de la cámara, y el ancho y la altura definen el ancho y la altura del rectángulo en comparación con el tamaño real de la pantalla. La configuración predeterminada de la ventanilla es (0,0, 0,0, 1,0, 1,0), que cubre toda la pantalla. Para la cámara de seguimiento, estableceré el ancho y altura en un 30 % de la pantalla:

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

En la Figura 5 se muestra el aspecto de la vista del juego después del lanzamiento de la bola. Observe la vista en la esquina inferior izquierda.

Efecto imagen en imagen con la cámara de seguimiento
Figura 5 Efecto imagen en imagen con la cámara de seguimiento

El control de entrada (qué pantalla reacciona a los eventos de entrada, como eventos de puntero o teclado) permanecerá con la cámara libre definida en la primera parte de este tutorial. Esto ya se estableció en la función init.

Desarrollar aún más el juego

Al principio, indiqué que este juego sería un prototipo. Hay un par de cosas más que se deben implementar para convertirlo en un verdadero juego.

Lo primero sería agregar una GUI que solicite el nombre del usuario y, por ejemplo, muestre las opciones de configuración (quizás podemos hacer una partida del juego de bolos en Marte, lo que tan solo implicará ajustar la gravedad) y cualquier otra cosa que necesite el usuario.

Babylon.js no ofrece una manera nativa de crear una GUI. Sin embargo, los miembros de la comunidad crearon unas cuantas extensiones que puede usar para crear estupendas GUI, por ejemplo CastorGUI (bit.ly/1M2xEhD), bGUi (bit.ly/1LCR6jk) y la extensión de cuadro de diálogo bit.ly/1MUKpXH. La primera usa HTML y CSS para agregar capas por encima del lienzo 3D. Las demás, agregan cuadros de diálogo en 3D a la escena en sí mediante mallas normales y texturas dinámicas. Recomiendo que pruebe estas antes de escribir su propia solución. Todas están listas para usar y simplificarán significativamente el proceso de creación de la GUI.

Otra mejora sería usar mejores mallas. Las mallas de mi juego se crearon con las funciones internas de Babylon.js, y es evidente que hay un límite en lo que se puede conseguir con solo código. Hay muchos sitios que ofrecen objetos 3D gratuitos y de pago. El mejor, en mi opinión, es TurboSquid (bit.ly/1jSrTZy). Para conseguir un mejor rendimiento, busque mallas con pocos polígonos.

Y después de agregar mejores mallas, ¿por qué no agregar un objeto humano que realmente lanzará la bola? Para ello, necesitará la característica de animación de huesos integrada en Babylon.js y una malla que la admita. Encontrará una demostración que muestra su aspecto en babylonjs.com/BONES.

Como toque final, intente hacer que el juego admita la realidad virtual. El único cambio necesario en este caso sería la cámara que se usa. Reemplace el objeto FreeCamera por un objeto WebVRFreeCamera y observe lo fácil que es usar Google Cardboard.

Hay más mejoras que puede hacer: agregar una cámara sobre los bolos, agregar más pistas para ofrecer una funcionalidad multijugador, limitar el movimiento y la posición de la cámara desde los que se lanzará la bola y mucho más. Dejaré que descubra algunas de esas características usted mismo.

Resumen

Espero que se haya divertido con este tutorial tanto como yo disfruté al escribirlo. Espero que le dé el impulso en la buena dirección y que lo anime a probar Babylon.js. Es un marco realmente estupendo, creado con amor por los desarrolladores para los desarrolladores. Visite babylonjs.com para obtener más demostraciones del marco. Y, una vez más, no dude en unirse al foro de soporte técnico y publicar sus dudas. ¡No hay preguntas absurdas, solo respuestas cómicas!


Raanan Weberes consultor de TI, desarrollador de pila completa, marido y padre. En su tiempo libre, contribuye con Babylon.js y otros proyectos de código abierto. Puede leer su blog en la dirección blog.raananweber.com.

Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: David Catuhe