Categorías: Labs

Construir Cardboard Dungeon con A-Frame

Esta es una traducción del artículo original publicado en el blog de Mozilla Hacks. Traducción por Rober Villar.

Cardboard Dungeon (Mazmorra de Cartón) es una experiencia web de movimiento por mazmorras diseñada para ser usada con Google Cardboard y escrita utilizando el framework de realidad virtual de Mozilla, A-Frame.

En este caso de estudio, voy a hablar de los principales desafíos a los que me enfrenté durante el desarrollo de Cardboard Dungeon, mis experiencias con A-Frame, y algunas de las lecciones que aprendí mientras experimentaba con la realidad virtual por primera vez.

Empezando con A-Frame

Me topé con A-Frame buscando un modo fácil de comenzar con el desarrollo de la realidad virtual. A-Frame me atrajo porque encaja naturalmente con los conceptos de desarrollo web a los que estoy acostumbrado. La capacidad de situar entidades dentro de una escena a través de markup puro es extremadamente potente y ofrece una barrera muy baja de acceso. También ayuda que la documentación de A-Frame sea clara y concisa – algo que es tan importante para mí como un desarrollador que opta a utilizar código de terceros / librerías.

Honestamente,me sorpendió lo robusto que es A-Frame . La mayoría de los obstáculos a los que me tuve que enfrentar estaban relacionados con el reto específico del manejo de la realidad virtual.

Construcción de una mazmorra

Área de renderizado

Cardboard Dungeon comenzó como una manera rápida de probar algunas de las características de A-Frame. En lugar de crear por adelantado una mazmorra completa, mi concepto fue tener un número fijo de habitaciones definiendo el área reproducible alrededor del jugador. Esas habitaciones pueden ser reproducidas basándose en datos de un archivo JSON. Esto puede reducir el número de entidades dentro del DOM y proporciona una mazmorra extremadamente grande si así lo quisiera, con muy poco o ningún impacto en el rendimiento.

Una habitación es sencilla y siempre está compuesta como máximo por cuatro paredes, un suelo, y un techo. Los datos JSON definen cuáles de ellos deben ser reproducidos para cada habitación. También opté por un sencillo sistema de cuadrícula para definir la posición virtual de la habitación – con (0,0,0) siendo el punto de salida del jugador.

Inicialmente inyectaba nuevas entidades A-Frame cada vez que el jugador se ponía en movimiento. Sin embargo, hablando con el equipo A-Frame, me señalaron al componente “visible”. Terminé inicializando la reproducción de cada espacio por adelantado, alternando después el componente “visible” para cada habitación cuando entra el jugador.

// llamado una vez durante la inicialización de la escena.
Container.prototype.init = function () {
  var entity = document.createElement('a-entity');
  entity.className = 'top';
  entity.setAttribute('mixin','wall top');
  entity.setAttribute('visible', 'false');
  entity.setAttribute('position', {
    x: this.position_multipliers.x,
    y: (4 + this.position_multipliers.y),
    z: this.position_multipliers.z
  });
  document.getElementById(this.target).appendChild(entity);
  // …
};
 
// llamado cuando el jugador se mueve.
Container.prototype.render = function () {
  // establecer el componente `visible` a las entidades de este contenedor.
  var container = document.getElementById(this.target);
  if (this.room) {
    setAttributeForClass(container, 'top', 'visible', (this.room.data.top ? this.room.data.top : 'false'));
    setAttributeForClass(container, 'bottom', 'visible', (this.room.data.bottom ? this.room.data.bottom : 'false'));
    setAttributeForClass(container, 'left', 'visible', (this.room.data.left ? this.room.data.left : 'false'));
    setAttributeForClass(container, 'right', 'visible', (this.room.data.right ? this.room.data.right : 'false'));
    setAttributeForClass(container, 'back', 'visible', (this.room.data.back ? this.room.data.back : 'false'));
    setAttributeForClass(container, 'front', 'visible', (this.room.data.front ? this.room.data.front : 'false'));
  }
  // …
};
 
function setAttributeForClass (parent, class_name, attribute, value) {
  var elements = parent.getElementsByClassName(class_name);
  for (var i = 0; i < elements.length; i++) {
    elements[i].setAttribute(attribute, value);
  }
}

Para comenzar, estuve reproduciendo un área de 3×3 alrededor del jugador pero la aumenté a 3×3×3 para permitir el recorrido vertical. También lo extendí a 2 cuadrículas en las direcciones norte, sur, este, y oeste para ayudar con la ilusión de la distancia.

Lección de VR #1: Escala

La escala en la pantalla no es igual que en un dispositivo de cabeza VR. En una pantalla las alturas pueden verse bien, pero un dispositivo VR amarrado a la cabeza puede alterar drásticamente la percepción de la escala. Esto todavía está presente sutilmente en Cardboard Dungeon, especialmente cuando se atraviesa verticalmente, debido a que las paredes pueden parecer más altas de lo previsto. Fue importante para poner a prueba con frecuencia las experiencias en el dispositivo VR.

Recorrido

Globos de recorrido

El recorrido del mapa fue una de las primeras cosas que necesité resolver. Como todas las cosas en VR, requirió de bastantes iteraciones.

Inicialmente utilicé cuadrículas sobre el suelo (N, E, S, O) para activar el movimiento del jugador. Esto funcionó bien, y entonces iteré sobre esto para proporcionar controles adicionales para el recorrido vertical. Lo hice sensitivo al contexto para que la opción de movimiento vertical apareciera únicamente cuando fuera necesario. Sin embargo, esto resultó en mucha revisión visual de los alrededores y depender que el jugador reconociera los controles.

Lección de VR #2: Esfuerzo

Situar interacciones comunes fuera de la mirada de los jugadores crea una incómoda experiencia. Tener que mirar al suelo con el objeto de activar el movimiento implica constantemente mover la cabeza hacia adelante y hacia atrás. Situando esta interacción cercana a la posición de mirada de descanso de los jugadores se crea una experiencia mucho más confortable.

Mi solución final fue por lo tanto utilizar una mecánica de teleportación. El jugador simplemente mira a una esfera azul para moverse a ese lugar, independientemente de que la habitación este en un piso más alto o más bajo. Opté por limitar esto a un cuadro alrededor del jugador, con objeto de retener la sensación de exploración.

function move (dom_element) {
  // obtener los IDs de la habitación actual y la habitación destino.
  var current_room_key_array = containers.center.room_id.split(',');
  var container_key = dom_element.parentElement.getAttribute('id');
  var target_room_key_array = containers[container_key].room_id.split(',');
 
  // calcular las diferencias de distancia.
  var offset_x = parseInt(target_room_key_array[0], 10) - parseInt(current_room_key_array[0], 10);
  var offset_y = parseInt(target_room_key_array[1], 10) - parseInt(current_room_key_array[1], 10);
  var offset_z = parseInt(target_room_key_array[2], 10) - parseInt(current_room_key_array[2], 10);
 
  // aplicar a cada habitación.
  Object.keys(containers).forEach(function (key) {
    var container = containers[key];
    var room_key_array = container.room_id.split(',');
    room_key_array[0] = parseInt(room_key_array[0], 10) + offset_x;
    room_key_array[1] = parseInt(room_key_array[1], 10) + offset_y;
    room_key_array[2] = parseInt(room_key_array[2], 10) + offset_z;
    var new_room_key = room_key_array.join(',');
 
    if (map[new_room_key]) {
      container.room = new Room(map[new_room_key].data);
      container.room_id = new_room_key;
 
      // remover los ítemes existentes.
      container.removeItems();
 
      // agregar ítem si existe en los datos de la nueva habitación.
      if (map[new_room_key].item) {
        container.addItem(map[new_room_key].item);
      }
 
      container.render();
    } else {
      container.room = null;
      container.room_id = new_room_key;
 
      // remover los ítemes existentes.
      container.removeItems();
      container.render();
    }
  });
}

Inventario e interacción

Inventario

El inventario y la interacción llevaron el mayor esfuerzo e iteración para crear algo funcional. Experimenté con muchas ideas locas, como encoger al jugador en una caja de inventario a sus pies o teleportarlo a una habitación inventario separada.

Aunque es divertido, esos prototipos destacan el tema de la conveniencia dentro de la realidad virtual. Los conceptos pueden ser divertidos para explorar como experiencias iniciales, pero las mecánicas poco familiares pueden convertirse en un inconveniente y volverse irritantes al final.

Lección de VR #3: Movimiento automatizado

Tomar el control del jugador crea una mala experiencia. En el caso de Cardboard Dungeon, la mencionada mecánica de encogimiento tenía una animación que escalaba la cámara y la movía a una caja a los pies del jugador. Esto rápidamente generó una sensación de nausea porque el jugador no tiene control sobre la animación; es una acción poco natural.

Al final, quedé con el método más conveniente de interacción para el jugador. Esto fue sencillamente una cuadrícula de elementos a los pies del jugador. Colocando los elementos situados en la mazmorra en una cuadrícula desde la cual los elementos pueden ser sencillamente seleccionados. Algunas veces, la solución más sencilla proporciona la mejor experiencia.

Conclusión

Disfruté muchísimo utilizando A-Frame para crear mi juego. Es un poderoso framework, y creo que es una excelente herramienta de prototipado rápido, además de ser una útil herramienta de producción por derecho propio.

Me preocupaba que la realidad virtual basada en la web pudiera sufrir problemas de rendimiento, pero quedé complacido al observar que éste no era el caso. El tamaño de las texturas causó el principal problema de rendimiento, porque introdujeron vibraciones y causaron un notable impacto en la latencia.

Lo que es genial en A-Frame es la posibilidad de crear tus propios componentes para aumentar las entidades y componentes ya existentes. No he tenido la oportunidad de experimentar demasiado con este concepto, pero obviamente éste es el siguiente paso para mejorar la experiencia en Cardboard Dungeon.

Como apunte final, el equipo A-Frame y la comunidad son una maravilla. Su grupo de Slack es muy activo, y los miembros del equipo son extraordinariamente responsivos.

Espero que ésto dé una visión del reto al que tuve que hacer frente cuando construí Cardboard Dungeon. La realidad virtual es una frontera bastante nueva, y por lo tanto, las respuestas son pocas, con muchas lecciones todavía por aprender. Es un excitante espacio por explorar, y frameworks como A-Frame están ayudando a hacer la realidad virtual más accesible a los desarrolladores web que quieren explorar esta nueva frontera.

Puedes jugar Cardboard Dungeon aquí (recomiendo Google Cardboard) y el código fuente completo está disponible en GitHub.

Gracias por leerlo.

The following two tabs change content below.

jorgev

Add-ons Developer Relations Lead at Mozilla
Jorge trabaja para el equipo de complementos de Mozilla, y se dedica a Mozilla Hispano y Mozilla Costa Rica en su tiempo libre. Actualmente está encargado del blog de Mozilla Hispano Labs.