Creación de un juego WebGL multiplataforma con Babylon.js
Publicado: 2022-03-10Aquí hay un desafío para ti: ¿qué te parece construir un juego en 3D durante el fin de semana? Babylon.js es un marco de JavaScript para crear juegos 3D con HTML5, WebGL y Web Audio , creado por su servidor y el equipo de Babylon.js. Para celebrar la nueva versión 2.3 de la biblioteca, decidimos crear una nueva demostración llamada "Sponza" para resaltar lo que se puede hacer con el motor WebGL y HTML5 cuando se trata de crear grandes juegos hoy en día.
La idea era crear una experiencia uniforme, similar, si no idéntica, en todas las plataformas compatibles con WebGL e intentar alcanzar las características de las aplicaciones nativas. En este artículo, explicaré cómo funciona todo en conjunto, junto con los diversos desafíos que enfrentamos y las lecciones que aprendimos mientras lo construíamos.
Lectura adicional en SmashingMag:
- Creación de sombreadores con Babylon.js
- Uso de la API Gamepad en juegos web
- Introducción al modelado poligonal y Three.js
- Cómo crear una caja de ritmos receptiva de 8 bits
Para lograr este objetivo, Sponza utiliza una serie de características de HTML5 como WebGL, Web Audio, así como Pointer Events (ampliamente compatible ahora gracias a jQuery PEP polyfill), Gamepad API, IndexedDB, HTML5 AppCache, transición/animación CSS3, flexbox y pantalla completa. API. Puede probar la demostración de Sponza en su computadora de escritorio, dispositivo móvil o Xbox One.
Descubriendo la demostración
Primero, comenzará con una secuencia autoanimada dando los créditos a quien haya construido la escena. La mayoría de los miembros del equipo provienen de la escena de demostración. Descubrirá que esta es una parte importante de la cultura de los desarrolladores 3D. Por mi parte, yo estaba en Atari mientras que David Catuhe estaba en Amiga, que sigue siendo una fuente habitual de conflictos entre nosotros, lo creas o no. Estaba programando un poco pero principalmente componiendo la música en mi grupo de demostración. Era un gran admirador de Future Crew y, más específicamente, de Purple Motion, mi compositor favorito de escenas de demostración de todos los tiempos. Pero no nos desviemos del tema.
Para Sponza, aquí están los contribuyentes:
- Michel Rousseau , también conocido como "Mitch", ha realizado animaciones visuales notables y optimizaciones de renderizado actuando como artista 3D. Tomó el modelo Sponza proporcionado gratuitamente por Crytek en su sitio web y usó el exportador 3DS Max para generar lo que ves.
- David Catuhe , también conocido como "deltakosh", y yo hemos hecho la parte central del motor Babylon.js y también todo el código para la demostración (cargador personalizado, efectos especiales para el modo de demostración usando procesos posteriores de fundido a negro, etc.), así como también un nuevo tipo de cámara llamada “ UniversalCamera ” que maneja todo tipo de entradas de forma genérica.
- He compuesto la música usando Renoise y el banco de sonido EastWest Symphonic Orchestra. Si está interesado, ya he compartido mi flujo de trabajo y proceso en el artículo Componer la música para el juego de Windows 8 de World Monger usando el rastreador de Renoise y los complementos VST de East West.
- Julien Moreau-Mathis nos ayudó con la creación de una nueva herramienta para ayudar a los artistas 3D a finalizar el trabajo entre las herramientas de modelado (3DS Max, Blender) y el resultado final. Por ejemplo, Michel lo usó para probar y ajustar varias cámaras animadas e inyectar partículas en la escena.
Si espera hasta el final de la secuencia automática hasta el "final épico", se cambiará automáticamente al modo interactivo. Si desea omitir el modo de demostración, simplemente haga clic en el ícono de la cámara o presione A
en su gamepad.
En el modo interactivo, si está en una Mac o PC, podrá moverse dentro de la escena usando el teclado/ratón como un juego FPS. Si está en un teléfono inteligente, podrá moverse con un solo toque (y 2
para girar la cámara). Finalmente, en una Xbox One, puede usar el gamepad (o el escritorio si está conectando un gamepad). Dato curioso: en una PC táctil con Windows, puede usar potencialmente 3 tipos de entrada al mismo tiempo.
El ambiente es diferente en el modo interactivo. Tiene tres fuentes de audio de tormenta ubicadas aleatoriamente en el entorno 3D, golpes de viento y grietas de fuego en cada esquina. En los navegadores compatibles (Chrome, Firefox, Opera y Safari), incluso puede cambiar entre el modo de altavoz normal y el modo de auriculares haciendo clic en el icono dedicado. Luego utilizará la reproducción de audio binaural de Web Audio para una simulación de audio más realista, si está escuchando a través de auriculares.
Para tener una experiencia completa similar a la de una aplicación, hemos generado íconos y mosaicos para todas las plataformas. Esto significa, por ejemplo, que en Windows 8 ⁄ 10 puede fijar la aplicación web en el menú "Inicio". Incluso tenemos varios tamaños disponibles:
¡Desconectado primero!
Una vez que la demostración se haya cargado por completo, puede cambiar su teléfono al modo avión para cortar la conectividad y hacer clic en el icono de Sponza. La aplicación web seguirá brindando la experiencia completa con renderizado WebGL, audio web 3D y soporte táctil. Cambie a pantalla completa y literalmente no podrá sentir la diferencia entre la demostración y una experiencia de aplicación nativa.
Estamos usando la capa IndexedDB disponible de forma nativa dentro de Babylon.js para eso. La escena (formato JSON) y los recursos (texturas JPG/PNG, así como MP3 para la música y los sonidos) se almacenan en IDB. La capa IDB, junto con la memoria caché de la aplicación HTML5, proporciona la experiencia fuera de línea. Para obtener más información sobre esta parte y cómo configurar su juego para obtener resultados similares, puede leer el artículo sobre Uso de IndexedDB para manejar sus activos 3D WebGL: compartir comentarios y sugerencias de Babylon.JS y almacenamiento en caché de recursos en IndexedDB en Babylon.js
Xbox One disfruta del espectáculo
Por último, pero no menos importante, la misma demostración funciona perfectamente en MS Edge en tu Xbox One:
Presione A
para cambiar al modo interactivo . Xbox One te notificará que ahora puedes moverte usando tu gamepad dentro de la escena 3D:
Entonces, recapitulemos brevemente.
¡La misma base de código funciona en Mac, Linux, Windows en MS Edge, Chrome, Firefox, Opera y Safari, en iPhone/iPad, en dispositivos Android con Chrome o Firefox, Firefox OS y en Xbox One! ¿No es genial? ¿Ser capaz de dirigirse a tantos dispositivos con una experiencia nativa completamente desarrollada directamente desde su servidor web?
Ya he compartido mi entusiasmo por el potencial de la tecnología en un artículo anterior en La web: ¿la próxima frontera del juego?
Hackear la escena con la capa de depuración
Si desea comprender cómo Michel está dominando la magia del modelado 3D, puede piratear la escena con la herramienta Capa de depuración de Babylon.js. Para habilitarlo en una máquina con teclado, presione CMD/CTRL + SHIFT + D
y si está usando un gamepad en PC o Xbox, presione Y
Tenga en cuenta que mostrar la capa de depuración cuesta un poco de rendimiento debido al trabajo de composición que debe realizar el motor de renderizado. Por lo tanto, los FPS que se muestran son un poco menos importantes que los FPS reales que tiene sin mostrar la capa de depuración.
Vamos a probarlo en una PC, por ejemplo.
Acérquese a la cabeza del león y corte el canal de protuberancia de la tubería de nuestro sombreador:
Deberías ver que la cabeza ahora es menos realista. Juega con el otro canal para ver qué está pasando.
También puede apagar el motor de rayos dinámicos o desactivar el motor de colisiones para volar o moverse a través de las paredes. Por ejemplo, desactive la casilla de verificación " colisiones " y vuele al primer piso. Pon la cámara frente a las banderas rojas. Puedes verlos moverse ligeramente. Michel ha utilizado el soporte de huesos/esqueletos de Babylon.js para moverlos. Ahora, deshabilite la opción " esqueletos " y deberían dejar de moverse:
Por fin, puede mostrar el árbol de mallas en la esquina superior derecha. Puede habilitarlos o deshabilitarlos para interrumpir completamente el trabajo realizado por Michel:
Eliminar las geometrías, los canales del sombreador o algunas opciones del motor puede ayudarlo a solucionar problemas de rendimiento en un dispositivo específico para ver qué está costando demasiado actualmente. También puede verificar si tiene una CPU limitada o una GPU limitada, aunque la mayoría de las veces, tendrá una CPU limitada en WebGL debido a la naturaleza de subprocesamiento único de JavaScript. Finalmente, la herramienta también es muy útil para ayudarlo a aprender cómo el artista 3D ha construido una escena.
Por cierto, también funciona bastante bien en Xbox One:
Desafíos
En el camino, enfrentamos una serie de problemas y desafíos para construir la demostración. Veamos algunos de ellos en detalle.
Rendimiento WebGL y compatibilidad multiplataforma
El lado de la programación fue probablemente el más fácil de abordar, ya que el propio motor Babylon.js lo maneja completamente. Estamos utilizando una arquitectura de sombreador personalizada que se adapta a la plataforma al tratar de encontrar el mejor sombreador disponible para el navegador/GPU actual usando varias alternativas. La idea es bajar la calidad y complejidad del motor de renderizado hasta que logremos mostrar algo significativo en la pantalla.
Babylon.js se basa principalmente en WebGL 1.0 para garantizar que las experiencias 3D creadas sobre él funcionen prácticamente en todas partes. Ha sido construido con la filosofía web en mente, por lo que estamos mejorando progresivamente el proceso de compilación de shaders. Esto es completamente transparente para el artista 3D que no quiere lidiar con estas complejidades la mayor parte del tiempo.
Aún así, el artista 3D tiene un papel muy importante en la optimización del rendimiento. Tiene que conocer la plataforma a la que se dirige, las características admitidas y las limitaciones. No puede tomar activos provenientes de juegos AAA creados para GPU de gama alta y DirectX 12 e integrarlos en un juego que se ejecuta en un motor WebGL. Yo diría que apuntar a WebGL hoy es bastante similar al trabajo que tendrá que hacer para optimizar las experiencias en dispositivos móviles, con una pizca de JavaScript adicional que tiene que ser altamente monoproceso.
Mitch es extremadamente bueno en eso: optimizar las texturas, precalcular el rayo para hornearlo en las texturas, reducir la cantidad de sorteos tanto como sea posible, etc. Tiene años de experiencia a sus espaldas y vio las diversas generaciones de Hardware y motores 3D (desde PowerVR/3DFX hasta las GPU actuales) que realmente ayudaron a hacer realidad la demostración.
Ya compartió un poco de esos conceptos básicos en sus artículos sobre 3D en tiempo real: hacer una demostración para WebGL Propósitos: conceptos básicos y ya demostró varias veces que puede crear una experiencia visual bastante fascinante en la web con alto rendimiento en pequeñas GPU integradas, por ejemplo, en Escenas de demostración de Mansion, Hill Valley o Espilit. Si está interesado, tómese el tiempo para ver su charla sobre NGF2014: cree activos 3D para el mundo móvil y la web, el punto de vista de un diseñador 3D donde compartió su experiencia y cómo logró optimizar la escena de Hill Valley desde menos de 1 fps a 60 fps.
El objetivo inicial de Sponza era construir dos escenas. Uno para escritorio y otro para móvil con menos complejidad, texturas más pequeñas y mallas y geometrías más simples. Pero durante nuestras pruebas, finalmente descubrimos que la versión de escritorio también funcionaba bastante bien en dispositivos móviles, ya que puede funcionar hasta 60 fps en un iPhone 6s o un Android OnePlus 2. Entonces decidimos no seguir trabajando en la versión móvil más simple.
Pero, de nuevo, probablemente hubiera sido mejor tener un enfoque móvil primero limpio en Sponza para alcanzar más de 30 fps en muchos dispositivos móviles y luego mejorar la escena para dispositivos móviles y de escritorio de alta gama. Aún así, la mayoría de los comentarios que hemos recibido hasta ahora en Twitter parecen indicar que el resultado final funciona muy bien en la mayoría de los dispositivos. Es cierto que Sponza se ha optimizado en una GPU HD4000 (Intel Core i5 integrado) que es más o menos equivalente a las GPU reales de los móviles de gama alta.
Estamos bastante contentos con el rendimiento que logramos lograr. Sponza está usando nuestro sombreador con ambient , difuso , bump , especular y reflejo habilitado. Tenemos algunas partículas para simular pequeños incendios en cada esquina, huesos animados para las banderas rojas, sonidos posicionados en 3D y colisiones cuando te mueves usando el modo interactivo.
Técnicamente hablando, tenemos 98 mallas utilizadas en la escena , generando hasta 377781 vértices, 16 huesos activos, más de 60 partículas que podrían generar hasta 36 llamadas de dibujo. ¿Qué hemos aprendido? Una cosa es segura: tener menos sorteos es la clave para un rendimiento óptimo, más aún en la web.
el cargador
Para Sponza, queríamos crear un nuevo cargador, diferente al predeterminado que usamos en el sitio web de BabylonJS, para tener una aplicación web limpia y pulida. Luego le pedí a Michel que sugiriera algo nuevo.
Primero me envió la siguiente pantalla:
De hecho, la pantalla se ve muy bien cuando la miras por primera vez. Pero entonces podría comenzar a preguntarse cómo hará que funcione en todos los dispositivos, de una manera verdaderamente receptiva. Averigüémoslo.
Hablemos primero de los antecedentes. El efecto borroso creado por Michel era agradable, pero no funcionaba bien en todos los tamaños de ventana y resoluciones, lo que generaba muaré. Luego lo reemplacé con una captura de pantalla "clásica" de la escena. Sin embargo, quería que el fondo llenara completamente la pantalla sin barras negras y sin estirar la imagen para romper la proporción.
La solución proviene principalmente de CSS background-size: cover
+ centrar la imagen en los ejes X e Y. Como resultado, tenemos la experiencia que estaba buscando, independientemente de la relación de pantalla utilizada:
Las otras partes están utilizando el buen posicionamiento CSS basado en porcentajes. De acuerdo, con eso resuelto, ¿cómo manejamos la tipografía? El tamaño de fuente debe basarse en el tamaño de la ventana gráfica. Obviamente, podemos usar unidades de ventana gráfica para eso. vw
y vh
(donde 1vw es el 1 % del ancho de la ventana gráfica y 1vh es el 1 % de la altura de la ventana gráfica) se admiten bastante bien en todos los navegadores, en particular en todos los navegadores compatibles con WebGL. (También hay un artículo sobre la tipografía del tamaño de la ventana gráfica en la revista Smashing que le recomiendo que lea).
Finalmente, estamos jugando con la propiedad de opacity
de la imagen de fondo para moverla de 0
a 1
según el proceso de descarga actual que pasa de 0 a 100%.
Ah, por cierto, las animaciones de texto simplemente se realizan usando transiciones CSS o animaciones combinadas con un diseño de caja flexible para tener una forma simple pero eficiente de mostrar en el centro o en cada esquina.
Manejar todas las entradas de manera transparente
Nuestro motor WebGL está haciendo todo el trabajo en el lado de la representación para mostrar las imágenes correctamente en todas las plataformas. Pero, ¿cómo podemos garantizar que el usuario podrá moverse dentro de la escena sea cual sea el tipo de entrada que se utilice?
En la versión anterior de Babylon.js, admitíamos todos los tipos de entrada e interacciones del usuario: teclado/mouse, toque, joysticks táctiles virtuales, gamepad, orientación del dispositivo VR (para Card Board) y WebVR, cada uno a través de una cámara dedicada. Puede leer nuestra documentación para aprender un poco más sobre ellos.
Touch se administra universalmente con la especificación Pointer Events que se propaga a todas las plataformas a través de jQuery PEP polyfill (que genera Touch Events para la aplicación cuando es necesario). Para obtener más información sobre los eventos de puntero, puede leer Unificar el toque y el mouse: cómo los eventos de puntero facilitarán el soporte táctil de los navegadores cruzados.
Volvamos a la demostración entonces. La idea de Sponza era tener una cámara única, que manejara todos los escenarios de los usuarios a la vez: escritorio, móvil y consola.
Terminamos creando UniversalCamera . Para ser honesto, fue tan obvio y simple de crear que todavía no sé por qué no lo hicimos antes. La UniversalCamera es más o menos una cámara de gamepad que amplía la TouchCamera que amplía la FreeCamera .
FreeCamera proporciona la lógica del teclado/ratón; TouchCamera proporciona la lógica táctil y la extensión final proporciona la lógica del gamepad.
UniversalCamera ahora se usa en Babylon.js de forma predeterminada. Si está navegando por las demostraciones, puede moverse dentro de las escenas usando el mouse, el toque y el gamepad en todas ellas. Nuevamente, puede estudiar el código para ver cómo se hace exactamente.
Sincronizar las transiciones con la música
Ahora, esta parte es donde nos hemos hecho la mayoría de las preguntas. Es posible que haya notado que la secuencia de introducción está sincronizada con áreas específicas de la pista de reproducción de música . Las primeras líneas se muestran cuando suenan algunos de los tambores, y la secuencia final cambia rápidamente de una cámara a otra en cada nota del instrumento de viento que estamos usando.
Sincronizar audio con el bucle de renderizado WebGL no es fácil. Una vez más, esta es la naturaleza monohilo de JavaScript que genera esta complejidad. Los artículos sobre Introducción a HTML5 Web Workers: el enfoque de subprocesos múltiples de JavaScript comparten algunas ideas detrás de eso. Es realmente importante comprender el problema para comprender el problema global al que nos enfrentamos, pero entrar en detalles aquí está fuera del alcance de este artículo.
Por lo general, en las escenas de demostración (y los videojuegos), si desea sincronizar las imágenes con los sonidos/música, lo guiará la pila de audio. A menudo se utilizan dos enfoques:
- Genere metadatos que se inyectarían en los archivos de audio y que luego podrían "llamar" a algunos eventos cuando llegue a una parte específica del mismo.
- Análisis en tiempo real del flujo de audio a través de FFT o tecnologías similares para detectar picos interesantes o cambios de BPM que generarían nuevamente eventos para el motor visual.
Esos enfoques funcionan particularmente bien en entornos de subprocesos múltiples como C++. Pero en JavaScript, con Web Audio, tenemos dos problemas:
- JavaScript, que tiene un solo subproceso y, desafortunadamente, la mayoría de las veces los trabajadores web no nos ayudarán,
- Web Audio no tiene ningún evento que pueda enviarse de vuelta al subproceso de la interfaz de usuario, incluso si el navegador maneja Web Audio en un subproceso separado.
Web Audio tiene un temporizador mucho más preciso que JavaScript. Hubiera sido fantástico poder usar este temporizador separado en un subproceso separado para llevar los eventos de vuelta al subproceso de la interfaz de usuario. Pero hoy, no puedes hacer eso (¿todavía?).
Por otro lado, renderizamos la escena usando WebGL y el método requestAnimationFrame
. Esto significa que, en el "mejor de los casos", tenemos un marco de tiempo de ventanas de 16 ms. Si te falta uno, tendrás que esperar hasta 16 ms para poder actuar en el siguiente cuadro para reflejar la sincronización del sonido (por ejemplo, para lanzar un efecto de "fundido a negro").
Entonces estaba pensando en inyectar la lógica de sincronización en el bucle requestAnimationFrame
. Estudié el tiempo transcurrido desde el comienzo de la secuencia y analicé la opción de ajustar lo visual para reaccionar ante un evento de audio. La buena noticia es que el audio web reproducirá el sonido independientemente de lo que suceda en el subproceso principal de la interfaz de usuario. Por ejemplo, puede estar seguro de que la marca de tiempo de 12 segundos de la música llegará exactamente 12 segundos después de que la música haya comenzado a reproducirse, incluso si la GPU tiene dificultades para reproducir la escena.
Al final, finalmente elegimos probablemente la solución más simple: ¡usar llamadas a setTimeout()
! Sí, lo sé. Si observa la mayoría de los artículos, incluido el que vinculé anteriormente, descubrirá que es bastante poco confiable. Pero en nuestro caso, una vez que la escena está lista para ser renderizada, sabemos que hemos descargado todos nuestros recursos (texturas y sonidos) y compilado nuestros shaders. No deberíamos estar demasiado molestos por los eventos inesperados que saturan el subproceso de la interfaz de usuario. GC podría ser un problema , pero también hemos dedicado mucho tiempo a luchar contra él en el motor: reducir la presión sobre el recolector de elementos no utilizados mediante el uso de la barra de desarrollo F12 de Internet Explorer 11.
Aún así, sabemos que esta solución está lejos de ser la ideal. Cambiar a otra pestaña o bloquear su teléfono y desbloquearlo unos segundos más tarde podría generar algunos problemas en la parte de sincronización de la demostración. Podríamos solucionar estos problemas usando la API de visibilidad de página, por ejemplo, pausando el ciclo de renderizado, varios sonidos y volviendo a calcular los siguientes marcos de tiempo para las llamadas a setTimeout()
.
Pero tal vez nos hemos perdido algo; tal vez, e incluso probablemente, había un mejor enfoque para manejar este problema. Nos encantaría escuchar sus pensamientos y sugerencias en la sección de comentarios si cree que hay una mejor manera de resolver el mismo problema.
Manejo de audio web en iOS
El último desafío que me gustaría compartir con ustedes es la forma en que iOS maneja el audio web en iPhone y iPad. Si está buscando artículos sobre "audio web + iOS", encontrará muchas personas que tienen dificultades para reproducir sonidos en iOS. Ahora, ¿qué está pasando allí?
iOS tiene un notable soporte de audio web, incluso el modo de audio binaural. Pero Apple ha decidido que una página web no puede reproducir ningún sonido por defecto sin la interacción de un usuario específico. Esta decisión probablemente se tomó para evitar que la publicidad o cualquier otra cosa molestara al usuario al reproducir sonidos no solicitados.
¿Qué significa para los desarrolladores web? Bueno, primero debe desbloquear el contexto de audio web de iOS después del toque de un usuario, antes de intentar reproducir cualquier sonido. De lo contrario, su aplicación web permanecerá desesperadamente muda.
Desafortunadamente, la única forma que he encontrado para hacer esta verificación es mediante un enfoque de rastreo de plataforma de usuario, ya que no encontré una forma de detección de características para hacerlo. Eso es nada menos que una técnica horrible, y no a prueba de balas, pero no pude encontrar ninguna otra solución que resolviera el problema. ¿El código? ¡Aqui tienes!
Si no está en iPad/iPhone/iPod, el contexto de audio está disponible de inmediato para su uso. De lo contrario, estamos desbloqueando el contexto de audio de iOS al reproducir un sonido vacío generado por código en el evento del extremo táctil . Puede registrarse en el evento onAudioUnlocked si desea esperar antes de iniciar su juego. Entonces, si está ejecutando Sponza en un iPhone/iPad, tendrá esta pantalla final al final de la secuencia de carga:
Al tocar cualquier parte de la pantalla, se desbloqueará la pila de audio de iOS y se iniciará el "espectáculo".
¡Así que, aquí vamos! Espero que haya disfrutado de algunas ideas detrás del desarrollo de la demostración. Para obtener más información, lea el código fuente completo de esta demostración en nuestro GitHub. Obviamente, todo es de código abierto y puede encontrar los archivos principales en GitHub: index.js y babylon.demo.ts.
Finalmente, ¡realmente espero que ahora esté aún más convencido de que la web es definitivamente una gran plataforma para jugar! Estén atentos, ya que estamos trabajando en nuevas demostraciones en este mismo momento y esperamos que también sean bastante impresionantes.
Este artículo es parte de la serie de desarrollo web de los evangelistas tecnológicos e ingenieros de Microsoft sobre aprendizaje práctico de JavaScript, proyectos de código abierto y mejores prácticas de interoperabilidad, incluido el navegador Microsoft Edge.Lo alentamos a probar en navegadores y dispositivos, incluido Microsoft Edge, el navegador predeterminado para Windows 10, con herramientas gratuitas en dev.microsoftedge.com, incluidas las herramientas para desarrolladores F12: siete herramientas distintas y completamente documentadas para ayudarlo a depurar, probar y Acelera tus páginas web. Además, visite el blog de Edge para mantenerse actualizado e informado por los desarrolladores y expertos de Microsoft.