
En el
artículo anterior, intentamos crear una escena básica en A-Frame para probar los conceptos básicos del marco en la práctica. En este artículo, me gustaría compartir mi experiencia en la creación de un juego en A-Frame - ¡Reciclar! VR El repositorio del proyecto está disponible en el
siguiente enlace .
Reciclaje!
La idea de crear un juego surgió casi de inmediato tan pronto como me enteré de Web VR. Aunque en general, creo que los juegos en la web en cualquier caso serán inferiores a los buenos proyectos incluso para dispositivos móviles, sin mencionar las computadoras personales y las consolas. Pero, me parece, el juego es la prueba más difícil. Empecé a pensar exactamente en lo que puedo hacer. Vi otros proyectos e inmediatamente me sorprendió la oportunidad de tomar algo usando el controlador. Y dado que he estado conectado con una organización que se dedica a la recolección de basura por separado durante bastante tiempo, la respuesta vino por sí sola. Reciclaje Tomamos la basura y la tiramos a la basura. ¿Qué podría ser más fácil? Pero todo resultó no ser tan simple, y esto, de hecho, se discutirá más a fondo.
Selección de marco
En el momento del inicio del trabajo en el juego, solo conocía 2 marcos más o menos serios: React 360, A-Frame. Obviamente, A-Frame fue el más adecuado para crear el juego. Sí, ahora sé que todavía hay un motor de juego PlayCanvas que también admite VR, pero es demasiado tarde. Además, resultó que A-Frame tampoco es malo para crear juegos.
Por donde empezar
Comencé estudiando ejemplos oficiales de juegos de desarrolladores de A-Frame. El beneficio de estos no es suficiente. A-Blast, A-Painter, Museum, Super Craft y ahora también Gunters of Oasis. De todos los proyectos presentados, me gustó más A-Blast, un juego de disparos en el que tienes que luchar con las criaturas más lindas del universo. Por lo tanto, quería tomar este juego como plantilla para el mío. Pero no funcionó. Y la razón de esto fue la estructura del juego. Me pareció que estaba demasiado abarrotada y no pensada. Quizás no se requiera más, pero quería hacer algo más conveniente y más fácil de entender.
Estructura
La estructura
A-Blast representa solo un punto de entrada: el archivo index.html, que contiene una escena con todos los activos, entidades básicas del juego, controles y todo en general.

Como puede ver en la captura de pantalla, además de los componentes y sistemas necesarios (A-Frame usa el patrón Entity Component System), también hay balas y enemigos, esencialmente los mismos sistemas, pero por alguna razón tienen su propio contenedor. Generalmente diga que este código no es fácil de entender. Así que decidí pensar cómo se puede estructurar este código. La primera idea es dividir la escena en sus partes constituyentes. ¿Por qué serían útiles un enrutador y plantillas, que representarían esta o aquella parte de la escena? Después de buscar el primero y el segundo (no, no cinco minutos), no encontré nada. Aunque soy partidario de la regla, no escribo bicicletas, pero esta vez tuve que escribir mi decisión. Aunque, en 2-3 semanas, encontré plantillas de Kevin Ngo. Pero ya era demasiado tarde.
Router y patrones

Y así,
a-frame-router-templates entra en escena. Que puede hacer el? Como se mencionó anteriormente, su tarea principal es representar las partes necesarias del juego, por ejemplo, la pantalla de título, el campo de juego, la pantalla final del juego, etc. Como hacerlo En principio, puede encontrar todo lo que necesita en la documentación del módulo en github, pero en resumen, tenemos lo siguiente:
<a-scene router> ... <a-route id="start-screen" template="start-screen"></a-route> <a-route id="game-field" template="game-field"></a-route> <a-route id="game-over" template="game-over"></a-route> <a-route id="how-to-play" template="how-to-play"></a-route> ... <a-template name="controls"></a-template> ... </a-scene>
- Estamos agregando el componente del enrutador a la escena.
- Agregue una ruta para cada parte de la aplicación (fotogramas de la escena). Una ruta para la pantalla de inicio, otra para el campo de juego, etc.
- Renderizar plantillas directamente a través de a-templates
- Si es necesario, cambiamos rutas a través de
this.el.systems.router.changeRoute('game-field');
Nota : este ejemplo se refiere al código de escena, por lo que podemos llamar al sistema del enrutador directamente. - Configuramos y conectamos las plantillas, algo como esto:
AFRAME.registerTemplate('game-field', ` <a-sub-assets> <a-asset-item id="glass" src="/assets/models/glass_bottle.gltf"></a-asset-item> ... <audio id="fail" src="/assets/sounds/fail.wav" preload></audio> </a-sub-assets> <a-template name="button" options="text: EXIT; position: 0 1 4; rotation: 0 180 0; event: stop-game"></a-template> <a-entity id="indicator" indicator visible="false" position="0 1 -2" text="align: center; width: 4; color: #00A105; value: -1" ></a-entity> <a-entity game-field-manager></a-entity> `);
Nota: los activos secundarios le permiten cargar activos así como activos activos, pero solo con la diferencia de que hay una verificación por defecto y si el activo ya está agregado, no se agregará nuevamente cuando se cambie la ruta.
Nota 2: Normalmente, puede usar plantillas solo con la cadena de plantilla ES6. De lo contrario, puede convertirse en "cadena" + var + "cadena", no genial. Kevin, por ejemplo, tiene soporte para motores de plantillas. Pero por qué complicarlo, ¿verdad?
Por lo tanto, puede crear una estructura de aplicación conveniente que contendrá lo siguiente:
componentes, sistemas, plantillas, estados, libs . Nada más y todo está en los estantes.
Manipulando objetos

La primera tarea que debía resolverse era la manipulación de objetos. Necesitaba un funcional como agarrar - lanzar. Inicialmente, comencé a reflexionar sobre cómo crear tal componente desde cero. Puramente a nivel filisteo, tal reflexión es permisible: tenemos un controlador (en el caso de un escritorio es un cursor), tiene una posición. También tenemos ciertos objetos, por ejemplo cubos, también tienen una posición. Al cambiar la posición del controlador, debemos cambiar la posición del objeto. Es simple? Entonces, en realidad, sí, pero no funcionará. Mencionaré un par de puntos de una lista muy larga para convencerlo de esto:
- El cursor en A-Frame es un descendiente de una cámara y tiene coordenadas relativas;
- La posición del controlador no es suficiente, aún debe considerar la orientación, la distancia al objeto, la posición de la cámara (reproductor);
- Para objetos con un cuerpo físico, esto no funcionará en absoluto, porque las coordenadas de la geometría están conectadas con las coordenadas del cuerpo.
Es bueno que el buen Sr. Wil Murphy y sus amigos hicieran
un marco de super manos . En esencia, esta biblioteca contiene todos los componentes necesarios:
- hoverable Orientación Apunte el controlador o cursor sobre la zona de colisión del objeto (generalmente todo el objeto)
- capturable : captura. Agarra un objeto usando el botón apropiado y arrástralo
- estirable : agarre con ambas manos y estire \ exprima
- arrastrable \ dropable : Esencialmente necesario para determinar el evento "el elemento fue arrojado a una ubicación específica"
Puede encontrar todo lo que necesita sobre la configuración y conexión de super-manos en el repositorio mencionado anteriormente. Solo quiero llamar la atención sobre una serie de matices:
- Crea mixins separados para la mano derecha e izquierda. Componentes separados por tipo de dispositivos compatibles. Por ejemplo, la mano derecha, además de oculus-touch, vive-controls, windows-motion-controls también puede ser oculus-go-controls y gear-vr-controls. La mano izquierda debe estar oculta para los cascos móviles BP. Cada controlador debe contener ambas manos mixinas y un componente de super manos. Un ejemplo ;
- Si especificó objetos: .clsname para reycaster, no olvide agregarlo a cada elemento que pueda tomarse usando el controlador, de lo contrario, no fallará ningún evento para super-manos. Por supuesto, si colliderEvent: raycaster-intersection ;
- Arrastrar con el mouse proyecta coordenadas 2D en el mundo 3D, por lo que es mejor usar el cursor para el escritorio.
Añadir física
Agregar física a un marco es en realidad muy simple. Hay un
sistema especial para esto. Se agrega a la escena y listo, la física ya está en tu bolsillo.
<a-scene physics="debug: false"> <a-box dynamic-body position="0 1 -2"></a-box> <a-box id="floor" static-body></a-box> </a-scene>
Marca :
debug: true permite ver cuerpos físicos ligados a la geometría. Es conveniente cuando necesita "delinear" un objeto.
De hecho, este es un envoltorio para
cannon.js que hace todo el trabajo sucio de comparar geometría y cuerpos físicos por usted. Nuevamente, sobre cómo funciona este sistema, puede encontrarlo en la descripción del repositorio. Y me gustaría detenerme en un solo punto que es importante para mi juego.
Necesitaba asegurarme de que al presionar el botón de la basura se estableciera una determinada fuerza (cuanto más se mantiene presionado el botón, mayor es la fuerza). Al final resultó que, esta tarea no es tan simple como parece a primera vista. Bueno, ¿qué es tan complicado? - usted dice,
aplicamos Impluse y listo. En realidad no ... Establece la rotación de un objeto a lo largo de un vector aplicado al centro del cuerpo. Usando este método, solo podemos emular un yule. Sin embargo, si establece un vector con el ángulo correcto con respecto al plano, puede obtener algo similar a un empuje. Pero esto no es lo que necesitaba.
Al final resultó que necesitaba velocidad al configurar este parámetro, el objeto comienza su movimiento en una dirección determinada. Esta dirección está especificada por el vector. Y aquí comienza la diversión. ¿Cómo encontrar este vector? Encontré dos opciones:
- Obtenga el cuaternión del controlador (o cámara para el escritorio), que describe su orientación en el espacio. Cree un vector V1 = <1,1,1>, multiplíquelo por la fuerza de lanzamiento y aplique la orientación a todo esto.
const velocityVector = new THREE.Vector3(1,1,1); velocityVector.multiplyScalar(this.force); velocityVector.applyQuaternion(controllerQuaternion); this.grabbed.body.velocity.set(velocityVector.x, velocityVector.y, velocityVector.z);
- Encuentre la posición del controlador (cursor) y la posición del objeto que se está lanzando. Calcule el vector de dirección para dos puntos. Normalizar el vector. Y multiplícalo por la fuerza.
const directionX = (trashPosition.x - zeroPosition.x); const directionZ = (trashPosition.z - zeroPosition.z); const vectorsLength = Math.sqrt(Math.pow(directionX, 2) + Math.pow(directionZ, 2)); const x = (directionX / vectorsLength) * this.force; const y = this.force; const z = (directionZ / vectorsLength) * this.force; this.grabbed.body.velocity.set(x , y, z );
Elegí la segunda opción porque solo puedo contar x y z. Y configúrelo usted mismo, ya que necesitaba un lanzamiento a lo largo del arco para que la basura arrojada cayera en la canasta, a pesar del usuario que sostenía el controlador.
Algunas palabras sobre el modelo.

Desde el principio, decidí hacer un juego de estilo
low-poly . Aunque WebGL es capaz de representar escenas relativamente complejas en la actualidad, su rendimiento sigue siendo inferior al de las bibliotecas avanzadas como DirectX, Vulkan, Mantle, etc. También depende del rendimiento del dispositivo del usuario. Como me gustaría centrarme en los cascos móviles BP más asequibles (Oculus Go, Gear VR), creo que low-poly es una de las pocas soluciones para crear una aplicación o juego de realidad virtual. Aunque, por supuesto, todo depende del volumen.
De acuerdo, low-poly es muy low-poly, pero ¿cómo hacerlo todo? Todo es muy simple, hay una buena herramienta de código abierto:
Blender . Créeme, él es capaz de mucho, pero para tareas simples no es del todo adecuado. Hay muchos materiales de capacitación relacionados con el modelado en Blender y no será difícil encontrarlos. Solo quería centrar su atención en una serie de puntos relacionados con el desarrollo web:
- El exportador Three-js no está actualizado. Necesidad de encontrar y suministrar exportador GLTF . GLTF es un formato especial diseñado para la web. Y sí, este es JSON.
- GLTF no es compatible con Cycles Renderer, por lo que debe usar Blender Renderer. Y esto significa que no habrá nudos geniales, transformaciones de color, reflejos metálicos (se puede hacer de manera diferente).
- Necesita exportar solo el elemento seleccionado. ¿No necesitas cámaras y luces adicionales? Archivo> Exportar> gltf 2.0. En el menú de la izquierda, Exportar GLTF 2.0> Exportar solo seleccionado.
- Comenzamos a exportar desde la posición <0, 0, 0> en Blender. Es mejor escalar en el mismo lugar para que luego no use el componente de escala en un marco.
- Si dibujas espacios abiertos como en Reciclar! VR, solo debes agregar objetos donde el jugador pueda mirar teóricamente. Detrás, detrás de las casas, en el Reciclaje! hay un par de árboles y solo en el lugar donde el usuario puede verlos. No es necesario sobrecargar la escena.
- Si necesita cambiar el material del modelo, debe esperar hasta que se cargue, obtener el modelo en sí, extraer todos los nodos (GLTF contiene información no solo sobre mallas)
e.detail.model.traverse((node) => { if (node.isMesh) { node.material.color = new THREE.Color(someColor); } });
En conclusión
¡Gracias a todos por su atención! Les recuerdo una vez más que el repositorio del proyecto está disponible en el
siguiente enlace . Cualquiera que quiera aportar algo nuevo a este juego, bienvenido.