Juegos 3D en Javascript de Instagram, o ruta de vuelo de salchichas



Después del primer artículo sobre la programación de juegos con máscaras en Instagram, un cliente me pidió que creara un juego en Instagram para su pizzería. Este juego fue planeado para ser utilizado con fines de promoción comercial. Por supuesto, entiendo que, a juzgar por la cantidad de visitas del primer artículo, el tema de Instagram no es particularmente interesante para la comunidad de Habr. Aparentemente, este servicio todavía se considera una especie de entretenimiento frívolo para las rubias, y un artículo sobre él solo es adecuado como lectura de viernes para un cóctel nocturno. Sin embargo, hoy, solo viernes. Consigue un cóctel. Y no olvides invitar a las rubias. Sin embargo, es probable que en el futuro la tecnología alcance tal altura que comencemos a jugar en Instagram en GTA-5. O 10.

Este artículo discutirá dos juegos a la vez. Hice uno para ordenar, y luego el segundo para mí. Con cada nuevo juego, me enfrentaba a nuevas tareas, la búsqueda de soluciones que me hicieron conocer los nuevos matices del desarrollo. Quizás esta historia sea útil para otros desarrolladores de elementos de realidad aumentada. Los juegos están escritos en Javascript puro sin el uso de ninguna biblioteca adicional. Para mostrar gráficos en 3D, se utilizan las herramientas integradas de Instagram.

Juego 1. Pizza


Entonces, ¿cuál es la esencia del primer juego? Spinning pizza. Los componentes salen volando de su superficie: trozos de salchicha, tomates, etc. Dos jugadores deben atrapar su boca. El ganador es el que, como puede suponer, atrapará más. Con el tiempo, la velocidad de rotación aumenta. Lo más divertido de este juego para mí fue programar las rutas de vuelo de estos elementos comestibles. Imagina programar un vuelo de salchicha. No, definitivamente, nunca me había divertido tanto con la programación. Me río incluso ahora, escribiendo este artículo.

Cuando el cliente vio el prototipo funcional, me envió esto: ":))". Hubo un problema al probar el resultado final. Consistió en el hecho de que los sujetos no podían jugar el juego: simplemente estaban llenos de risas, fue muy divertido.

Permíteme recordarte que el desarrollo de máscaras y juegos se realiza en Spark AR Studio. Y luego desde allí puedes subir tu trabajo en Instagram para que todos lo vean. Es de destacar que las tecnologías web difuminan los límites entre los sistemas operativos para un desarrollador. Escribe un código, que luego funciona en la aplicación de Instagram para iOS y Android, sin modificaciones ni cambios. Por supuesto, el pago de esto se convierte en una velocidad de script fundamentalmente baja.

El horario del juego fue proporcionado por el cliente. Hubo algunas dificultades con los modelos 3D. En particular, al usar el motor de animación incorporado Spark AR Studio, resultó que si el objeto en movimiento se escala, entonces sus coordenadas se determinan incorrectamente en el juego. Pero este efecto molesto no se observa si no escala el objeto completo por completo, sino cada una de sus mallas por separado. Tuve que dejar la escala de pizza 1: 1, y prescribir un cierto coeficiente para cada elemento que lo hace. También hubo problemas con el formato FBX en el que necesita exportar modelos para un juego de Instagram. El diseñador gráfico envió modelos con texturas incorporadas, que, además, se colocaron a lo largo de un camino relativo. El editor 3D los vio, pero Spark AR no. Tuve que volver a embalar los modelos para que los archivos de textura se separen de los modelos y sigan un camino con ellos.

Y otro pequeño problema que encontré fue que el objeto API Saprk AR responsable de mostrar el texto en la pantalla se negó a aceptar el valor como un valor, por ejemplo, una puntuación de juego. Durante mucho tiempo no pude entender por qué no se muestra nada. ¿Qué estoy haciendo mal? Resultó que primero necesitas convertir los números en cadenas (.toString ()). Esto es evidente para otros idiomas, pero es bastante atípico para javascript, que siempre lo ha hecho por sí mismo.

Animación simple


Una de las cosas nuevas para mí en este juego fue programar la animación de objetos 3D. (En mi anterior juego de Instagram "Tic-Tac-Toe", no había animación alguna). El motor de animación en Spark AR Studio es muy específico. Toma algunos parámetros de entrada y luego conecta de forma reactiva la variable con el objeto en el que desea cambiar algo. Por ejemplo, esto cambiará la coordenada y (startValue, endValue - sus valores iniciales y finales) de algún objeto 3D a lo largo del tiempo t:

var driverParameters = {
    durationMilliseconds: t,
    loopCount: Infinity,
    mirror: false
};
var driver = Animation.timeDriver(driverParameters);
var sampler = Animation.samplers.linear(startValue, endValue);
sceneObject.transform.y = Animation.animate(driver, sampler);
driver.start();

Para mover los ingredientes de la pizza volando en el espacio, decidí simplemente ejecutar tres de esas animaciones en paralelo para cada coordenada. Fue suficiente para indicar la coordenada inicial (startValue) y la coordenada final calculada por el ángulo de rotación de la pizza (endValue), de modo que estaba en algún lugar lejano en caso de que el jugador no atrapara este "caparazón" con su boca. Si es atrapado, entonces el movimiento se detiene. El evento de apertura de boca que ya describí en un artículo anterior. Solo aquí hay un juego para dos y, en consecuencia, ya habrá dos caras y dos bocas:

FaceTracking.face(0).mouth.openness.monitor().subscribe(function(event) {
    if (event.newValue > 0.2) {
        ...
    };
});

FaceTracking.face(1).mouth.openness.monitor().subscribe(function(event) {
    if (event.newValue > 0.2) {
        ...
    };
});

El punto clave en este juego es atrapar ingredientes voladores con la boca, en otras palabras, encontrar un objeto volador en un área pequeña determinada alrededor del centro de la boca de una persona. Al principio, este cálculo no quería hacerse correctamente para mí, pero se encontró una solución al problema después de la introducción de un objeto oculto atado a las coordenadas de la boca, y funcionó. Por alguna razón, las coordenadas directas de la boca no regresaron. Aquí cameraTransform.applyTo es la reducción de las coordenadas de los puntos faciales a las coordenadas en el mundo 3D (el método se toma de la documentación).

move: function() {
    //   (   )

    //    
    var object = Scene.root.find('pizzafly');
    var olast = {
        x: object.transform.x.pinLastValue(),
        y: object.transform.y.pinLastValue(),
        z: object.transform.z.pinLastValue()
    };

    //   ,       
    var objectHidden = Scene.root.find('nullObject');
    objectHidden.transform.x = FaceTracking.face(face).cameraTransform.applyTo(FaceTracking.face(face).mouth.center).x;
    objectHidden.transform.y = FaceTracking.face(face).cameraTransform.applyTo(FaceTracking.face(face).mouth.center).y;
    objectHidden.transform.z = FaceTracking.face(face).cameraTransform.applyTo(FaceTracking.face(face).mouth.center).z;

    //   
    var mouth = {
        x: objectHidden.transform.x.pinLastValue(),
        y: objectHidden.transform.y.pinLastValue(),
        z: objectHidden.transform.z.pinLastValue()
    };

    //    
    var d = {
        x: Math.abs(olast.x - mouth.x),
        y: Math.abs(olast.y - mouth.y),
        z: Math.abs(olast.z - mouth.z)
    };

    //  
    if ((d.x > 0.03) || (d.y > 0.03) || (d.z > 0.03)) {
        // 
        ...
    } else {
        // 
        ...
    };

},

Posteriormente, me di cuenta de que debería eliminar la verificación de profundidad (coordenada z), ya que en este juego en particular es visualmente difícil estimar la profundidad. Es decir, ahora fue posible atrapar el ingrediente abriendo la boca en cualquier momento durante el vuelo. Lo principal es la combinación de x e y.


Finalmente, el último problema que encontré al escribir este juego fue limitar el tamaño de la compilación final a 4 MB. Además, de acuerdo con la recomendación de Facebook, para que el juego se muestre en tantos dispositivos móviles como sea posible, es conveniente incluir incluso 2 MB. Y los modeladores 3D son personas creativas y quieren hacer modelos pesados ​​con una grilla densa y texturas enormes, sin preocuparse por nosotros, los programadores o, más bien, por el rendimiento final en los juegos. También de alguna manera reduje el tamaño de las texturas y comprimí en jpg (en lugar de png), pero el modelo de pizza en sí tuvo que enviarse para su revisión (retopología). Como resultado, sin embargo, fue posible encajar en el volumen de 2MB con todos los modelos, texturas y un script. Y el juego fue con moderación.

Y después de un tiempo regresó con la redacción de que "el efecto contiene demasiado texto estático". Tuve que eliminar los números de cuenta regresiva y, en lugar de ellos, establecer la animación en la flecha del cronómetro, que ahora comenzó a contar el tiempo en lugar de ellos. Después de eso, el juego fue aprobado.

Juego 2. Sobre la mariposa (ButterFlap)



Ahora hablaré sobre crear un segundo juego. No puedo evitar hacer juegos, este es mi hobby. Unos días antes del 8 de marzo, decidí crear un juego de Instagram para estas vacaciones. Algo sobre flores, mariposas y dulces. Pero no podía pensar cuál podría ser la esencia del juego. La idea giraba en mi cabeza. ¿Quizás la mariposa recogerá dulces y los pondrá en una canasta? ¿O tal vez las mariposas recogerán dulces en el aire y los arrojarán, y el jugador deberá atraparlos con la boca? En general, estafé durante un par de días y, al no encontrar una solución, me di cuenta de que para el 8 de marzo, mi juego aún no tendría tiempo de pasar por la moderación, ya que el último demora de 3 a 4 días. Ya quería dejar esta empresa, cuando de repente la idea surgió por sí sola, de repente. Ya no até el juego al Día de la Mujer, ahora era solo un juego.

Scroller De izquierda a derecha, el paisaje y los diversos obstáculos se mueven. El jugador debe controlar una mariposa que puede moverse libremente dentro de la pantalla y recoger diamantes que cuelgan en el aire. Además, de derecha a izquierda un poco más rápido que el resto de la imagen, las nubes se mueven, de las cuales llueve a cántaros. Necesitas esconderte de la lluvia debajo de las flores. Si una mariposa cae bajo el agua, sus alas se mojan y cae, este es el final del juego. Llamé al juego simplemente: ButterFlap.

Sí, todo esto suena bien. Yo, directamente, quería jugar solo. Pero recordé que no era artista en absoluto. Sin embargo, puedo hacer modelos 3D, y esto es más fácil que dibujar. Entonces, se decidió, dejar que haya un desplazamiento 3D.

Artes graficas


Encontré modelos libres de regalías en línea que tuve que refinar. Puso las texturas sobre las que no tenían texturas, ya que las había colocado previamente en un atlas de texturas: los artefactos en forma de "escaleras" son visibles en los modelos sin textura, y el suavizado se puede configurar para las texturas en el juego, lo que hace que la apariencia de los objetos sea más presentable y no tan plana. Aquellos modelos que contenían texturas también tenían sus defectos que tenían que ser reparados. En primer lugar, el tamaño de las texturas no era un múltiplo de dos en el poder. Es decir, por ejemplo, podría ser igual a 1200x1200. Tuve que comprimir a 1024x1024. Los procesadores de video pueden escalar o completar texturas inapropiadas con espacio vacío, es decir, aquellos que no son 1024x1024, 512x512, 256x256, etc. En cualquier caso, tales acciones son una carga adicional durante el trabajo del juego y un consumo de memoria sin sentido,por lo tanto, es mejor preparar las imágenes correctas primero manualmente. La situación también se salvó por el hecho de que distribuí todas las texturas por atlas de texturas, por lo que si, por ejemplo, la textura original tenía un tamaño de 400x200 píxeles, entonces podría ponerla en el atlas 1024x1024, como está, junto a otras similares. Después de esto, es naturalmente necesario escalar también la exploración UV, pero esto se hace en un par de segundos. Todavía encontré variantes de modelos donde, por alguna razón, las texturas estaban ligadas a lo largo de un camino absoluto como "C: \ Work \ Vasya \ Map.jpg". Bueno, ¡no hay tal carpeta en mi computadora! Tenía que especificar las rutas de acceso a las texturas manualmente ... ¿Pero por qué se te ocurrió la idea de que debería mantener todos mis proyectos en la unidad "C:"? Oh, estos modeladores, artistas libres ... Por cierto, de esta manera, por los nombres de las carpetas en rutas aleatorias, puedes aprender inadvertidamente más sobre la identidad del modelador,Por ejemplo, su nombre. Encantada de conocerte!

Lo más difícil fue encontrar un modelo de mariposa adecuado con animación. Spark AR usa animación que se puede exportar desde cualquier editor 3D al formato FBX. Las alas de un par de modelos de mariposas que descargué estaban de alguna manera extrañamente reflejadas: un ala fue modelada y la segunda se reflejó de alguna manera que no entendí, y, por lo tanto, dos de ellas resultaron. Con este enfoque de modelado, al final, en el juego, una de las alas (copiada) no quería recibir luz de la fuente y siempre permanecía tenue. No me arriesgué a cambiar drásticamente el modelo, porque entonces la animación habría volado. Y en animación soy un novato aún más grande que en modelado 3D. Quizás el problema era otra cosa: intenté, por ejemplo, expandir las normales, pero eso no ayudó. En resumen, los modelos 3D gratuitos son una molestia. Como resultado, habiendo lavado toda la noche,por prueba y error, encontré un modelo adecuado, que después de algunos ajustes con un archivo comenzó a parecer satisfactorio. Algo que no me gustó, pero fueron pequeñas cosas: rehice la textura, cambié la geometría en algunos lugares y ajusté los parámetros del material. Finalmente, la mariposa se fue. ¡Hurra! Pero ahora estoy agotado. Entonces decidí acostarme y continuar al día siguiente. Sí, pasé 7-8 días creando el juego. Pero fue un trabajo bastante pausado por las tardes, leyendo documentación, artículos y encontrando respuestas a preguntas.Entonces decidí acostarme y continuar al día siguiente. Sí, pasé 7-8 días creando el juego. Pero fue un trabajo bastante pausado por las tardes, leyendo documentación, artículos y encontrando respuestas a preguntas.Entonces decidí acostarme y continuar al día siguiente. Sí, pasé 7-8 días creando el juego. Pero fue un trabajo bastante pausado por las tardes, leyendo documentación, artículos y encontrando respuestas a preguntas.


Toda la tarde del día siguiente trabajé en gráficos. La especificidad de las máscaras de juego para Instagram, como ya he mencionado, es que es aconsejable no ir más allá del volumen de 2 MB para todo el juego (máximo 4 MB). No estaba preocupado por el script: es poco probable que su tamaño supere los 50 Kb. Pero sobre modelos 3D de plantas tuvieron que ser bastante evocados. Por ejemplo, esa parte del girasol donde se encuentran las semillas se hizo por geometría. Cientos de polígonos ... Reemplácelos con un fragmento de una esfera de un par de docenas de triángulos y estire la textura descargada, de hecho, de esta parte. El número de hojas también se puede reducir. Hacemos hierba en la parte inferior de la escena con un plano de dos triángulos con una textura superpuesta con un canal alfa. Alcanzamos el volumen con sombras y copiando fragmentos de la imagen dentro de la textura misma.

En general, es deseable minimizar el número de texturas, así como su tamaño en bytes. Son las texturas las que crean la mayor parte del juego. Estoy acostumbrado a las texturas donde se necesita transparencia, colocadas en un atlas de texturas - en png 32 bit, y las texturas que no usan el canal alfa, empaquetar en otro atlas, guardándolo en jpg. Jpg puede reducirse más fuerte que png. Los elementos Grass y UI que necesitan transparencia se han ido al atlas png, y todo lo demás a jpg.

El volumen total. En total, obtuve 4 tipos de plantas, una roca, una mariposa, que el jugador controlará, y una nube. Las texturas de todos estos modelos en forma comprimida tomaron 2 atlas 1024x1024 jpg y png con un volumen total de 500 Kb. Los modelos mismos tomaron alrededor de 200 Kb. Además de un guión. Sonidos - 31 Kb. Total: aproximadamente 1 Mb. En verde, solo se muestra el tamaño de la compilación (esto es lo que debería caber en 2 MB).


De repente me encontré con un problema completamente inesperado. Eliminé varios modelos no utilizados, pero no a través de Spark AR Studio, sino del sistema de archivos. Posteriormente, al ensamblar la construcción, descubrí que desaparecieron de la escena de Spark AR Studio, pero aún así entraron en el ensamblaje. Y limpiar el proyecto de los recursos no utilizados es imposible. Los enlaces a ellos desde el "Resumen de activos" no conducen a ninguna parte. Aparentemente, este es un defecto en Spark AR Studio. Tuve que recrear el proyecto nuevamente, agregando todos los recursos necesarios allí desde el principio.

Escena


He estado pensando durante mucho tiempo sobre cómo implementar el desplazamiento de todos los objetos en la pantalla. De las herramientas para esto, solo hay javascript y el motor de animación incorporado más simple en Spark AR Studio, que simplemente puede cambiar las coordenadas del objeto desde el valor inicial hasta el final en un momento dado.

Sí, el dilema aún surgió antes de esto: si crear el nivel completo del juego, una escena en el editor 3D, duplicando las plantas repetidas el número requerido de veces y colocándolas en las posiciones correctas, para desplazar toda la escena en el juego, o cargar solo una copia de cada 3D objetar y sustituirlos en la dirección del jugador justo fuera de la pantalla. La respuesta fue obvia. Nuestra elección es la segunda opción. De lo contrario, definitivamente no es posible caber en 2 MB: lo más probable es que la escena según la primera opción sea pesada. Pero entonces necesitas un diseño de objetos. Y yo, sin dudarlo, decidí usar el editor 3D como editor de niveles. Sí, resulta que hice las dos cosas. Plantas para el juego, guardé cada una en una copia. Y del editor solo necesitaba las coordenadas. Después de completar el trabajo,Escribí las coordenadas de todos los objetos e hice una matriz con los datos del juego.

lv:[
    {n:'flower1', x:8.0, y:-6.5},
    {n:'cloud', x:10.0, y:0.1},
    {n:'rain', x:10.0, y:6.6},
    {n:'flower1', x:14, y:-2.5},
    {n:'diamond_red', x:20, y:2.0},
	...
],

Y, sin embargo, una matriz asociativa separada, de acuerdo con los tipos de objetos, donde se almacenan los tamaños de los colisionadores y sus desplazamientos en relación con los centros de los objetos 3D, así como una bandera (col) que determina si el objeto es un obstáculo (de lo contrario, el jugador lo atraviesa). Para algunos tipos de objetos, se establece la bandera (destruir), que determina si el objeto debe ocultarse después de la interacción, así como el parámetro (v), que determina cierto grado de interacción, por ejemplo, el número de puntos que un jugador gana o pierde.

colld:{
    'flower1': {dx:0, dy:5, w:2.3, h:1.4, col:true},
    'diamond_green': {dx:0, dy:0, w:1, h:1, col:false, v:1, destroy:true},
    'diamond_red':{dx:0, dy:0, w:1, h:1, col:false, v:-2},

    ...
},

Un pequeño matiz. La hierba en la parte inferior de la escena debe desplazarse continuamente. Por lo tanto, debe usar dos copias de este objeto y sustituirlas una tras otra a medida que se mueve. Las flores aparecerán en una pantalla no más que en una copia cada una.

Teniendo en cuenta el problema de escala que encontré al desarrollar el primer juego, restablecí la escala de todos los modelos en el editor 3D. Por lo tanto, en los modelos Saprk AR se cargan inmediatamente en tamaños normales. Es cierto, de todos modos, en el guión, no podría prescindir del "número mágico", el coeficiente global, el código universal, que contiene la esencia del universo. Un universo virtual, por supuesto. Y estoy listo para revelarte este número. ¡Úselo, gente! ¡No me importa! Este número es 0.023423. En resumen, a pesar del restablecimiento de todas las escalas, un metro en el editor 3D resultó ser igual a este número en Spark AR Studio. Sin embargo, lo más probable es que no entiendo completamente todas las complejidades de trabajar con gráficos 3D, de ahí el coeficiente. Las coordenadas (pero no el tamaño) de todos los objetos que se exportaron desde el editor se multiplican por él, lo adivinó.Dónde ajustar la escala de la escena en Spark AR, no lo encontré.

El siguiente problema que encontré fue la pérdida del orden de los objetos al exportar desde un editor 3D. Los objetos complejos que consisten en varias mallas podrían aparecer de manera impredecible en la escena del juego de tal manera que, por ejemplo, una malla que estaba detrás de otra saltó repentinamente hacia adelante. Y, si observa el orden de los objetos después de exportar a Spark AR Studio, puede ver que esta malla es, por alguna razón, más alta en la lista, aunque fue más baja en el editor. Resolví este problema dividiendo la escena en capas y guardándolas en diferentes archivos.

Animación compleja


Durante otras tres noches, jugué con la programación. Si utilicé el motor estándar Spark AR Studio para animar las alas de una mariposa, la tarea de mover el fondo no fue tan simple. Todavía no entendía cómo adjuntar no solo un parámetro variable a las iteraciones del ciclo de movimiento, sino una función de devolución de llamada completa. En cualquier caso, no pude hacerlo, los parámetros de animación actuales no querían transferirse allí. Y tal función es simplemente necesaria, porque si en el primer juego (con pizza) verifiqué la colisión por el evento de abrir la boca suscribiéndome, entonces no hubo tal evento aquí. Y solo era necesario verificar las colisiones con objetos ambientales a medida que el personaje se mueve, de acuerdo con sus coordenadas actuales. Y para esto, las coordenadas deben compararse en cada iteración. Y luego pensé. Después de todo, ya escribí juegos en javascript.¿Y por qué no usar el motor de animación que escribí anteriormente para esos juegos? Su principio de funcionamiento es aproximadamente el mismo: en un momento dado, el parámetro (o parámetros) cambia entre los valores iniciales y finales dados. Y el valor actual se pasa como un parámetro, no lo creerá, en la función de devolución de llamada dada, en la que, por ejemplo, puede establecer un objeto 3D en la escena en coordenadas iguales a estos valores actuales, o verificar las colisiones en ellos. Gracias Cap. Tuve que adaptar ligeramente mi motor al "ecosistema" local: eliminar referencias al objeto de la ventana desde allí, ya que no está aquí, y otras pequeñas cosas.Y el valor actual se pasa como un parámetro, no lo creerá, en la función de devolución de llamada dada, en la que, por ejemplo, puede establecer un objeto 3D en la escena en coordenadas iguales a estos valores actuales, o verificar las colisiones en ellos. Gracias Cap. Tuve que adaptar ligeramente mi motor al "ecosistema" local: eliminar referencias al objeto de la ventana desde allí, ya que no está aquí, y otras pequeñas cosas.Y el valor actual se pasa como un parámetro, no lo creerá, en la función de devolución de llamada dada, en la que, por ejemplo, puede establecer un objeto 3D en la escena en coordenadas iguales a estos valores actuales, o verificar las colisiones en ellos. Gracias Cap. Tuve que adaptar ligeramente mi motor al "ecosistema" local: eliminar referencias al objeto de la ventana desde allí, ya que no está aquí, y otras pequeñas cosas.

Sí, todavía, sobre desplazarse por el paisaje y los objetos del entorno. Decidí poner todo el mundo en un NullObject, es decir, en un objeto 3D vacío, un contenedor, y moverlo usando solo un parámetro para la animación: su coordenada x. Dentro del contenedor, todos los modelos tienen las mismas coordenadas que si estuvieran afuera, solo que para ellos el sistema de referencia está vinculado a este objeto vacío. Las rocas y las flores se repetirán (además, a diferentes alturas del suelo, de acuerdo con el diagrama de nivel), para que pueda reutilizar estos objetos a medida que se mueve, estableciendo la posición horizontal y vertical deseada dentro del "contenedor". Escribí un sistema de búsqueda de objetos que caen en el marco (en el desplazamiento del contenedor actual), que establece el objeto que deja el marco en una nueva posición más si aparece allí. Puedes ver,cómo funciona en el ejemplo de tres objetos. (Habrá más de ellos en el juego, por lo que ya no verá ese efecto de "reorganización" de los objetos del entorno).



La función para actualizar las coordenadas de los objetos se ve así:

oCoordSet: function(v) {
    //  :    
    //   
    var camx = -ap.oWorld.transform.x.pinLastValue();
    //  
    var x1 = camx - ap.game.scro.w2;
    var x2 = camx + ap.game.scro.w2;
    //   
    for (var i = 0; i < ap.d.lv.length; i++) {
        //    
        if ((ap.d.lv[i].x >= x1) & (ap.d.lv[i].x <= x2)) {
            //   
            ap.d.lv[i].o = Scene.root.find(ap.d.lv[i].n);
            //  -  
            ap.d.lv[i].o.transform.y = ap.d.lv[i].y;
            if ((ap.d.lv[i].n == 'cloud') || (ap.d.lv[i].n == 'rain')) {
                //    ,
                //  
                //   2.3,
                //     
                ap.d.lv[i].o.transform.x = ap.d.lv[i].x - (x2 - ap.d.lv[i].x) * 2.3 + 0.2;
            } else {
                //    ,
                //      
                ap.d.lv[i].o.transform.x = ap.d.lv[i].x;
            };
        };
    };
    //        
    //     
    if (camx > ap.game.grassPosLast) {
        ap.game.grassPosLast += ap.game.grassd;
        ap.game.grassi = 1 - ap.game.grassi;
        ap[ap.game.grassNm[ap.game.grassi]].transform.x = ap.game.grassPosLast;
    };
},

El personaje principal


El personaje principal del juego es una mariposa insumergible, que vuela audazmente, superando obstáculos. Decidí hacer la gestión estableciendo un marcador o cursor (punto de luz), girando e inclinando la cabeza. Y en la dirección de este punto, una mariposa volará lentamente (de hecho, no es una luchadora, ni puede teletransportarse). Por ejemplo, si se suscribe al evento de inclinación de la cabeza, puede implementar un control vertical como este:

FaceTracking.face(0).cameraTransform.rotationX.monitor().subscribe(function(event) {
    var v = event.newValue;
    //  
    var scrH2 = ap.game.scr.h2;
    //
    var p = -v * 0.5;
    if (p < -scrH2) {
        p = -scrH2;
    } else if (p > scrH2) {
        p = scrH2;
    };
    //  
    var d = 0.006;
    //  
    var cur = ap.oPers.transform.y.pinLastValue();
    if (p < cur) {
        cur -= d;
        if (cur < p) {cur = p;};
    } else {
        cur += d;
        if (cur > p) {cur = p;};
    };
    //    ()
    ap.oPointer1.transform.y = p;
    //   ,
    // ,    
    ap.game.pers.dy + = cur - ap.game.pers.y;
});

Del mismo modo para el horizontal. Solo allí es necesario suscribirse al evento, no de una inclinación, sino de una rotación de la cabeza (rotaciónY), y en lugar de la altura de la pantalla, considere su ancho.

Colisionadores


Todo esto es maravilloso, el mundo se mueve y el personaje del juego puede moverse libremente por la pantalla. Pero ahora necesitas un controlador de colisión, de lo contrario, ningún juego funcionará. Hay tres eventos en el juego, según los cuales la posición del personaje puede cambiar. Esta es la rotación e inclinación de la cabeza, así como el movimiento del mundo, en el cual la coordenada horizontal del jugador (x) aumenta automáticamente.

Como no sé cómo funcionan las iteraciones del controlador de cara en Spark AR, ya sea que se llamen con una frecuencia determinada o estén configuradas con la frecuencia de reloj máxima posible, y en mi motor de animación puedo controlar este parámetro, decidí que definiría las colisiones en mi función El movimiento del mundo, que se llama a una frecuencia establecida por mí (60 cuadros por segundo). En eventos de procesamiento facial, solo "acumularemos" movimiento.

El principio será así. En eventos de inclinación de cabeza y giro de cabeza, se acumula un desplazamiento a lo largo de los ejes x e y. Además, en la función de desplazamiento por el mundo, se agrega un desplazamiento horizontal a la "alcancía". Y luego se verifica, si el desplazamiento acumulado se agrega a las coordenadas originales, entonces habrá una colisión con cualquiera de los objetos del mundo. Si no, entonces las coordenadas originales del jugador más el desplazamiento son hechas por las coordenadas actuales. Y luego actualizamos los de origen a los actuales (restablecer) y restablecemos las compensaciones. Si es así, retroceda las coordenadas al original. Además, necesitamos determinar qué eje sería la colisión, ya que ambas coordenadas no pueden revertirse. De lo contrario, el jugador simplemente "se queda" en un punto en el espacio y ya no puede moverse a ningún lado. Es necesario darle libertad de movimiento a lo largo del eje a lo largo del cual no se produce la colisión.Para que pueda darle la oportunidad de volar alrededor de un obstáculo.

setPersPos: function(camx, camdx) {
    //   
    //       (camx,camdx)

    //     
    var persx = ap.game.pers.x;
    var persy = ap.game.pers.y;
    var dx = ap.game.pers.dx;
    var dy = ap.game.pers.dy;

    // ,     
    var col = ap.collisionDetect(
        {x: persx, y: persy, dx: dx, dy: dy},
        {x: camx, dx: camdx, y: 0}
    );

    if (col.f == true) {
        // 

        if (col.colx == true) {
            //   
            //    ,
            //   
            //(    ,    )
            ap.game.pers.x = col.x;
        } else {
            //    ,
            //   
            ap.game.pers.x = persx + dx;
        };

        if (col.coly == true) {
            // -  
            ap.game.pers.y = col.y;
        } else {
            ap.game.pers.y = persy + dy;
        };

    } else {
        //  ,   
        ap.game.pers.x = persx + dx;
        ap.game.pers.y = persy + dy;
    };

    // 
    ap.game.pers.dx = 0;
    ap.game.pers.dy = 0;

    //     
    ap.oPers.transform.x = ap.game.pers.x;
    ap.oPers.transform.y = ap.game.pers.y;
},

La función de detección de colisión en sí:

collisionDetect: function(opers, ow) {
    // , opers -   , ow -  

    var res = {f: false, colx: false, coly: false, x: 0, y: 0};

    var ocoll, x, y, w, h, persx, persy, persx0, persy0, od, colx1, coly1, colx2, coly2;
    var collw = false, collh = false;

    //  
    //(  ,  "" )
    persx0 = opers.x + ow.x - ow.dx;
    persy0 = opers.y + ow.y;

    //       
    persx = persx0 + opers.dx;
    persy = persy0 + opers.dy;

    //  
    for (var i = 0; i < ap.d.lv.length; i++) {
        od = ap.d.lv[i]; //obj data

        //    (   ),
        //     
        //        
        if (typeof ap.d.colld[od.n] !== "undefined") {

            //       
            ocoll = ap.d.colld[od.n];
            colx1 = od.x + ocoll.x1;
            colx2 = od.x + ocoll.x2;
            coly1 = od.y + ocoll.y1;
            coly2 = od.y + ocoll.y2;

            if ((persx < colx1) || (persx > colx2) || (persy < coly1) || (persy > coly2)) {} else {
                //   
                res.f = true;

                //        ,
                //,    
                if ((persx0 < colx1) || (persx0 > colx2)) {
                    collw = true;
                };
                //        ,
                //,    
                if ((persy0 < coly1) || (persy0 > coly2)) {
                    collh = true;
                };

            };
        };

    };

    //   

    //  ,     ,
    //  
    if (collw == true) {
        res.colx = true;
        res.x = persx0 - ow.x;
    } else {
        res.x = opers.x;
    };

    // -  
    if (collh == true) {
        res.coly = true;
        res.y = persy0 + ow.y;
    } else {
        res.y = opers.y;
    };

    return res;
},

Lluvia


Utilicé el motor de partículas estándar Spark AR Studio para animar la lluvia. No hay nada especial aquí. Agregamos un objeto Emtter a la escena, sin olvidar preguntarle Emitter -> Space -> Local (en lugar de World). Esto es para asegurar que las gotas no se retrasen dinámicamente detrás de las nubes durante el movimiento, sino que siempre caigan directamente. Este método es más conveniente para una determinación más fácil del momento en que la mariposa cae bajo la lluvia, no es necesario hacer una corrección de altura. Para las gotas en sí, preparé la textura adecuada. Bueno y, por supuesto, el objeto de lluvia se moverá junto con el objeto de la nube. Luego agregué una condición de impacto en la nube al código de manejo de colisiones. Y, si no hay ningún obstáculo por encima del jugador en este momento, la mariposa cae y el juego termina.

Moderación


Para una moderación exitosa, el video obligatorio del juego no debe contener texto estático que no esté vinculado a objetos. De lo contrario, el robot detiene instantáneamente la moderación. Sin embargo, después de eso, una persona revisa el juego durante varios días. Y no le gustó la abundancia de texto antes del juego, así como al final. Tuve que reducir la cantidad de texto. Después de eso, el juego fue aprobado.

Resumen


No es que mi juego fuera particularmente emocionante. Probablemente carece de dinámica y mecánica adicional. Publicaré una actualización. Pero me di cuenta de que hacer juegos en Instagram es interesante. Este es un tipo de desafío. Realmente disfruté el proceso de programación y resolución de todo tipo de tareas bajo el minimalismo en términos de herramientas y la cantidad de memoria asignada. Recuerdo que alguien dijo que 640 Kb es suficiente para todos. Ahora intenta ajustar un juego 3D de 2 MB. Yo, tal vez, no argumentaré que son suficientes para todos ... ¡Pero pruébalo!


En conclusión, me gustaría reunir en una lista todos los momentos no obvios que encontré. Tal vez esto sea útil para alguien como una hoja de trucos al crear juegos para Instagram.

  • Escala. Ajuste la escala de todos los modelos 3D en el editor 3D para que esté inmediatamente 1: 1 en el escenario del juego.
  • . . , .
  • . FBX, . -. , .
  • . JPG , , , -, JPG, PNG.
  • . , , . Spark AR , . , , , 3D-.
  • 3D , : , . . .
  • , , . javascript .
  • En el contexto de Spark AR, no hay objetos de ventana y todo tipo de objetos específicos del navegador, como webkitRequestAnimationFrame, mozRequestAnimationFrame, etc. Este es un destino cuando se programa en animación javascript.

All Articles