Jeux 3D sur Instagram Javascript, ou trajectoire de vol de saucisse



Après le premier article sur la programmation de jeux en masques sur Instagram, un client m'a demandé de créer un jeu sur Instagram pour sa pizzeria. Ce jeu devait être utilisé à des fins de promotion commerciale. Bien sûr, je comprends que, à en juger par le nombre de vues du premier article, le thème Instagram n'est pas particulièrement intéressant pour la communauté Habr. Apparemment, ce service est toujours considéré comme une sorte de divertissement frivole pour les blondes, et un article à ce sujet ne convient que comme lecture du vendredi pour un cocktail en soirée. Cependant, aujourd'hui, juste vendredi. Obtenez un cocktail. Et n'oubliez pas d'inviter des blondes. Cependant, il est probable qu'à l'avenir la technologie atteindra des sommets tels que nous commencerons à jouer sur Instagram dans GTA-5. Ou 10.

Cet article abordera deux jeux à la fois. J'en ai fait un sur commande, puis le second pour moi. A chaque nouveau jeu, j'ai été confronté à de nouvelles tâches dont la recherche de solutions m'a fait connaître les nouvelles nuances du développement. Peut-être que cette histoire sera utile à d'autres développeurs d'éléments de réalité augmentée. Les jeux sont écrits en Javascript pur sans l'utilisation de bibliothèques supplémentaires. Pour afficher des graphiques 3D, les outils Instagram intégrés sont utilisés.

Jeu 1. Pizza


Alors, quelle est l'essence du premier match. Filature de pizza. Les composants s'envolent de sa surface - morceaux de saucisse, tomates, etc. Deux joueurs doivent attraper leur bouche. Le gagnant est celui qui, comme vous pouvez le deviner, en attrapera plus. Au fil du temps, la vitesse de rotation augmente. La chose la plus drôle dans ce jeu pour moi était de programmer les trajectoires de vol de ces éléments comestibles. Imaginez la programmation d'un vol de saucisses. Non, définitivement, je n'ai jamais eu autant de programmation amusante. Je ris encore maintenant en écrivant cet article.

Lorsque le client a vu le prototype fonctionnel, il m'a envoyé ceci: «:))». Un problème est survenu lors du test du résultat final. Cela consistait dans le fait que les sujets ne pouvaient pas jouer au jeu: ils éclataient simplement de rire - c'était tellement amusant.

Je vous rappelle que le développement des masques et des jeux se fait dans Spark AR Studio. Et à partir de là, vous pouvez télécharger votre travail sur Instagram pour que tout le monde puisse le voir. Il est à noter que les technologies Web brouillent les frontières entre les systèmes d'exploitation pour un développeur. Vous écrivez un code, qui fonctionne ensuite dans l'application Instagram pour iOS et Android, sans aucune modification ou changement. Bien sûr, le paiement pour cela devient une vitesse de script fondamentalement faible.

Le calendrier du jeu a été fourni par le client. Il y a eu quelques difficultés avec les modèles 3D. En particulier, lors de l'utilisation du moteur d'animation intégré Spark AR Studio, il s'est avéré que si l'objet en mouvement est mis à l'échelle, ses coordonnées sont incorrectement déterminées dans le jeu. Mais cet effet gênant n'est pas observé si vous ne redimensionnez pas complètement l'objet entier, mais chacun de son maillage séparément. J'ai dû quitter l'échelle de la pizza 1: 1, et prescrire un certain coefficient pour chaque élément qui en fait. Il y avait aussi des problèmes avec le format FBX dans lequel vous devez exporter des modèles pour un jeu Instagram. Le graphiste a envoyé des modèles avec des textures intégrées, qui, de plus, ont été placés le long d'un chemin relatif. L'éditeur 3D les a vus, mais pas Spark AR. J'ai dû reconditionner les modèles afin que les fichiers de texture soient séparés des modèles et le long d'un chemin avec eux.

Et un autre petit problème que j'ai rencontré était que l'objet api Saprk AR responsable de l'affichage du texte à l'écran refusait d'accepter la valeur comme une valeur - un score de jeu, par exemple. Pendant longtemps, je n'ai pas pu comprendre pourquoi rien ne s'affiche. Qu'est-ce que je fais mal? Il s'est avéré que vous devez d'abord convertir les nombres en chaînes (.toString ()). Cela va sans dire pour les autres langues, mais plutôt atypique pour le javascript, qui l'a toujours fait lui-même.

Animation simple


L'une des nouveautés pour moi dans ce jeu a été la programmation de l'animation d'objets 3D. (Dans mon précédent jeu Instagram «Tic-Tac-Toe», il n'y avait aucune animation.) Le moteur d'animation de Spark AR Studio est très spécifique. Il prend certains paramètres d'entrée, puis connecte de manière réactive la variable à l'objet dans lequel vous souhaitez modifier quelque chose. Par exemple, cela changera la coordonnée y (startValue, endValue - ses valeurs initiales et finales) d'un objet 3D au cours du temps 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();

Pour déplacer les ingrédients de la pizza dans l'espace, j'ai décidé de simplement exécuter trois de ces animations en parallèle pour chaque coordonnée. Il suffisait d'indiquer la coordonnée initiale (startValue) et la coordonnée finale calculée par l'angle de rotation de la pizza (endValue), pour que ce soit quelque part loin au cas où le joueur n'attraperait pas cette «coquille» avec sa bouche. S'il est pris - alors le mouvement s'arrête. L'événement d'ouverture de la bouche que j'ai déjà décrit dans un article précédent. Seulement voici un jeu pour deux et, par conséquent, il y aura déjà deux visages et deux bouches:

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

Le point clé de ce jeu est d'attraper des ingrédients volants avec votre bouche, en d'autres termes, de trouver un objet volant dans une petite zone donnée autour du centre de la bouche d'une personne. Au début, ce calcul ne voulait pas être fait correctement pour moi, mais une solution au problème a été trouvée après l'introduction d'un objet caché lié aux coordonnées de la bouche, et cela a fonctionné. Pour une raison quelconque, les coordonnées directes de la bouche ne sont pas revenues. Ici cameraTransform.applyTo est la réduction des coordonnées des points de face aux coordonnées dans le monde 3D (la méthode est tirée de la documentation).

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 {
        // 
        ...
    };

},

Par la suite, j'ai réalisé que vous devriez supprimer la vérification de la profondeur (coordonnée z), car dans ce jeu particulier, il est visuellement difficile d'estimer la profondeur. Autrement dit, il est désormais possible d'attraper l'ingrédient en ouvrant la bouche à tout moment pendant le vol. L'essentiel est la combinaison de x et y.


Enfin, le dernier problème que j'ai rencontré lors de l'écriture de ce jeu était de limiter la taille de la version finale à 4 Mo. De plus, selon la recommandation de Facebook, pour que le jeu soit affiché sur autant d'appareils mobiles que possible, il est souhaitable de contenir même 2 Mo. Et les modélisateurs 3D sont des gens créatifs et veulent faire des modèles lourds avec une grille dense et des textures énormes, sans se soucier complètement de nous, des programmeurs, ou plutôt de la performance finale dans les jeux. J'ai également en quelque sorte réduit la taille des textures et compressé en jpg (au lieu de png), mais le modèle de pizza lui-même a dû être envoyé pour révision (rétopologie). En conséquence, néanmoins, il a été possible de s'intégrer dans le volume de 2 Mo avec tous les modèles, textures et script. Et le jeu a continué avec modération.

Et après un certain temps, elle est revenue avec la formulation que "l'effet contient trop de texte statique". J'ai dû supprimer les chiffres du compte à rebours et au lieu d'eux, j'ai mis l'animation sur la flèche du chronomètre, qui a maintenant commencé à compter le temps à leur place. Après cela, le jeu a été approuvé.

Jeu 2. À propos du papillon (ButterFlap)



Je vais maintenant parler de la création d'un deuxième jeu. Je ne peux pas m'empêcher de faire des jeux, c'est mon hobby. Quelques jours avant le 8 mars, j'ai décidé de créer un jeu Instagram pour cette fête. Quelque chose au sujet des fleurs, des papillons et des bonbons. Mais je ne pouvais pas penser à ce que pourrait être l'essence du jeu. La pensée tournait dans ma tête. Peut-être que le papillon collectera des bonbons et les mettra dans un panier? Ou peut-être que les papillons ramasseront des bonbons en l'air et les jetteront, et le joueur devra les attraper avec sa bouche? En général, j'ai arnaqué pendant quelques jours et, n'ayant pas trouvé de solution, j'ai réalisé que le 8 mars, mon jeu n'aurait toujours pas le temps de passer par la modération, car cette dernière prend 3-4 jours. Je voulais déjà quitter cette aventure, quand soudain l'idée est venue d'elle-même, tout d'un coup. Je n'ai plus lié le match à la Journée de la femme, maintenant c'était juste un match.

Scroller. De gauche à droite, le paysage et divers obstacles se déplacent. Le joueur doit contrôler un papillon qui peut se déplacer librement dans l'écran et collecter des diamants suspendus dans les airs. De plus, de droite à gauche un peu plus vite que le reste de l'image, les nuages ​​se déplacent, d'où la pluie tombe. Vous devez vous cacher de la pluie sous les fleurs. Si un papillon tombe sous l'eau, ses ailes se mouillent et il tombe, c'est la fin du jeu. J'ai appelé le jeu simplement: ButterFlap.

Oui, tout cela sonne bien. Je voulais directement jouer moi-même. Mais je me suis souvenu que je n'étais pas du tout un artiste. Cependant, je suis capable de créer des modèles 3D, et c'est plus facile que de dessiner. Donc, il a été décidé, qu'il y ait un scroller 3D.

Arts graphiques


J'ai trouvé des modèles libres de droits en ligne que j'ai dû affiner. Il a tiré des textures sur celles qui étaient sans textures, après avoir placé celles-ci dans un atlas de texture: les artefacts sous forme d '«échelles» sont visibles sur les modèles non texturés, et le lissage peut être défini pour les textures dans le jeu, ce qui rend l'apparence des objets plus présentable et moins plate. Ces modèles qui contenaient des textures avaient également leurs défauts qui devaient être corrigés. Premièrement, la taille des textures n'était pas un multiple de deux en puissance. Autrement dit, il pourrait être égal à 1200x1200. J'ai dû compresser en 1024x1024. Les processeurs vidéo peuvent mettre à l'échelle ou remplir des textures inappropriées avec un espace vide, c'est-à-dire celles qui ne sont pas 1024x1024, 512x512, 256x256, etc. Dans tous les cas, ces actions sont une charge supplémentaire pendant le travail du jeu et une consommation de mémoire sans signification,par conséquent, il est préférable de préparer d'abord les images correctes manuellement. La situation a également été sauvée par le fait que j'ai distribué toutes les textures par des atlas de texture, donc si, par exemple, la texture d'origine était de 400x200 pixels, je pourrais simplement la mettre dans l'atlas 1024x1024, telle quelle, à côté d'autres similaires. Après cela, il est naturellement nécessaire de mettre à l'échelle le scan UV également, mais cela se fait en quelques secondes. Je suis toujours tombé sur des variantes de modèles où, pour une raison quelconque, les textures étaient liées le long d'un chemin absolu tel que «C: \ Work \ Vasya \ Map.jpg». Eh bien, il n'y a pas un tel dossier sur mon ordinateur! J'ai dû spécifier les chemins d'accès aux textures manuellement ... Mais pourquoi vous est venue l'idée que je devrais garder tous mes projets sur le lecteur "C:"!? Oh, ces modélistes, artistes libres ... Au fait, de cette façon, par les noms des dossiers dans des chemins laissés au hasard, vous pouvez en apprendre plus par inadvertance sur l'identité du modélisateur,par exemple, son nom. Ravi de vous rencontrer!

Le plus difficile a été de trouver un modèle papillon adapté avec animation. Spark AR utilise une animation qui peut être exportée de n'importe quel éditeur 3D au format FBX. Les ailes d'une paire de modèles de papillons que j'ai téléchargées étaient étrangement en miroir - une aile a été modélisée, et la seconde s'est reflétée d'une manière que je ne comprenais pas, et, par conséquent, deux d'entre elles se sont révélées. Avec cette approche de la modélisation, au final, dans le jeu, une des ailes (copiée) ne voulait pas recevoir de lumière de la source et restait toujours sombre. Je ne risquais pas de changer radicalement le modèle, car alors l'animation aurait volé. Et en animation, je suis un noob encore plus grand qu'en modélisation 3D. Peut-être que le problème était autre chose: j'ai essayé, par exemple, d'élargir les normales, mais cela n'a pas aidé. En bref, les modèles 3D gratuits sont pénibles. En conséquence, après avoir lavé toute la soirée,par essais et erreurs, j'ai trouvé un modèle approprié qui, après quelques ajustements avec un fichier, a commencé à avoir l'air satisfaisant. Quelque chose que je n’aimais pas, mais c’était de petites choses: j’ai refait la texture, changé la géométrie à certains endroits et ajusté les paramètres du matériau. Enfin, le papillon a décollé. Hourra. Mais maintenant, je suis épuisé. J'ai donc décidé d'aller me coucher et de continuer le lendemain. Oui, j'ai passé 7 à 8 jours à créer le jeu. Mais c'était un travail plutôt tranquille le soir, en lisant la documentation, les articles et en trouvant les réponses aux questions.J'ai donc décidé d'aller me coucher et de continuer le lendemain. Oui, j'ai passé 7 à 8 jours à créer le jeu. Mais c'était un travail plutôt tranquille le soir, en lisant la documentation, les articles et en trouvant les réponses aux questions.J'ai donc décidé d'aller me coucher et de continuer le lendemain. Oui, j'ai passé 7 à 8 jours à créer le jeu. Mais c'était un travail plutôt tranquille le soir, en lisant la documentation, les articles et en trouvant les réponses aux questions.


Toute la soirée du lendemain, j'ai travaillé sur le graphisme. La spécificité des masques de jeu pour Instagram, comme je l'ai déjà mentionné, est qu'il est conseillé de ne pas dépasser le volume de 2 Mo pour l'ensemble du jeu (maximum 4 Mo). Je ne m'inquiétais pas du script: sa taille ne devrait pas dépasser 50 Ko. Mais plus de modèles 3D de plantes devaient être assez conjurés. Par exemple, la partie du tournesol où se trouvent les graines a été réalisée par géométrie. Des centaines de polygones ... Remplacez-le par un fragment d'une sphère de quelques dizaines de triangles et étirez la texture téléchargée, en fait, de cette partie. Le nombre de feuilles peut également être réduit. Nous faisons de l'herbe au bas de la scène avec un plan de deux triangles avec une texture superposée avec un canal alpha. Nous atteignons le volume avec des ombres et des fragments de copie de l'image à l'intérieur de la texture elle-même.

En général, il est souhaitable de minimiser le nombre de textures, ainsi que leur taille en octets. Ce sont les textures qui créent l'essentiel du jeu. J'ai l'habitude des textures où la transparence est nécessaire, placées dans un atlas de texture - en png 32 bits, et les textures qui n'utilisent pas le canal alpha, emballées dans un autre atlas, en l'enregistrant en jpg. Jpg peut être réduit plus fort que png. Les éléments Grass et UI qui ont besoin de transparence sont passés à l'atlas png et tout le reste à jpg.

Le volume total. Au total, j'ai obtenu 4 types de plantes, un rocher, un papillon, que le joueur contrôlera, et un nuage. Les textures de tous ces modèles sous forme compressée ont pris 2 atlas 1024x1024 jpg et png avec un volume total de 500 Kb. Les modèles eux-mêmes prenaient environ 200 Ko. Plus un script. Sons - 31 Ko. Total: environ 1 Mo. En vert, la taille de la build est juste affichée (c'est ce qui devrait tenir dans 2 Mo).


Soudain, j'ai rencontré un problème complètement inattendu. J'ai supprimé plusieurs modèles inutilisés, mais pas via Spark AR Studio, mais du système de fichiers. Par la suite, lors de l'assemblage de la construction, j'ai constaté qu'ils avaient disparu de la scène Spark AR Studio, mais qu'ils étaient toujours dans l'assemblage. Et il est impossible de vider le projet des ressources inutilisées. Les liens vers eux à partir du «Résumé des actifs» ne mènent nulle part. Apparemment, c'est un défaut dans Spark AR Studio. J'ai dû recréer le projet à nouveau, en y ajoutant toutes les ressources nécessaires dès le début.

Scène


Je réfléchis depuis longtemps à la façon d'implémenter le défilement de tous les objets sur l'écran. Parmi les outils pour cela, il n'y a que javascript et le moteur d'animation intégré le plus simple dans Spark AR Studio, qui peut simplement changer les coordonnées de l'objet de la valeur initiale à la finale dans un temps donné.

Oui, il y avait encore un dilemme avant cela: créer le niveau entier du jeu, une scène dans l'éditeur 3D, en dupliquant les plantes répétitives le nombre de fois requis et en les plaçant dans les bonnes positions, afin de faire défiler la scène entière dans le jeu, ou de charger une seule copie de chaque 3D objet et les substituer dans la direction du joueur juste à l'extérieur de l'écran. La réponse était évidente. Notre choix est la deuxième option. Sinon, il ne sera certainement pas possible de tenir dans 2 Mo: très probablement, la scène selon la première option sera lourde. Mais alors vous avez besoin d'une disposition des objets. Et j'ai, sans hésitation, décidé d'utiliser l'éditeur 3D comme éditeur de niveau. Ouais, il s'avère que j'ai fait les deux. Plantes pour le jeu, je les ai sauvegardées en une seule copie. Et de l'éditeur, je n'avais besoin que des coordonnées. Après avoir terminé le travail,J'ai écrit les coordonnées de tous les objets et fait un tableau avec les données du jeu.

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},
	...
],

Et pourtant - un tableau associatif distinct, selon les types d'objets, où sont stockées les tailles des collisionneurs et leurs déplacements par rapport aux centres des objets 3D, ainsi qu'un drapeau (col) qui détermine si l'objet est un obstacle (sinon le joueur le traverse). Pour certains types d'objets, un indicateur (détruire) est défini, qui détermine si l'objet doit être masqué après interaction, ainsi que le paramètre (v), qui détermine un certain degré d'interaction, par exemple, le nombre de points qu'un joueur gagne ou perd.

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

    ...
},

Une petite nuance. L'herbe au bas de la scène doit défiler en continu. Vous devez donc utiliser deux copies de cet objet et les remplacer l'une après l'autre au fur et à mesure que vous vous déplacez. Les fleurs n'apparaîtront sur un écran que dans une copie chacune.

Compte tenu du problème d'échelle que j'ai rencontré lors du développement du premier jeu, j'ai réinitialisé l'échelle de tous les modèles dans l'éditeur 3D. Ainsi, dans les modèles Saprk AR immédiatement chargés dans des tailles normales. Certes, dans le scénario de toute façon, il ne pouvait se passer du «nombre magique», du coefficient global, du code universel, qui contient l'essence de l'univers. Un univers virtuel, bien sûr. Et je suis prêt à vous révéler ce numéro. Utilisez-le, les gens! Cela ne me dérange pas! Ce nombre est 0,023423. En bref, malgré la réinitialisation de toutes les échelles, un mètre dans l'éditeur 3D s'est avéré être égal à ce nombre dans Spark AR Studio. Très probablement, je ne comprends cependant pas pleinement toutes les subtilités du travail avec les graphiques 3D, d'où le coefficient. Les coordonnées (mais pas la taille) de tous les objets exportés depuis l'éditeur sont multipliées par lui, vous l'aurez deviné.Où ajuster l'échelle de la scène dans Spark AR, je n'ai pas trouvé.

Le problème suivant que j'ai rencontré était la perte de l'ordre de tri des objets lors de l'exportation à partir d'un éditeur 3D. Des objets complexes constitués de plusieurs mailles pouvaient apparaître de manière imprévisible sur la scène du jeu de telle manière que, par exemple, une maille qui se trouvait derrière une autre sautait soudainement en avant. Et, si vous regardez l'ordre des objets après l'exportation vers Spark AR Studio, alors, en effet, on peut voir que ce maillage est pour une raison quelconque plus haut dans la liste, bien qu'il soit plus bas dans l'éditeur. J'ai résolu ce problème en divisant la scène en couches et en les enregistrant dans différents fichiers.

Animation complexe


Pendant trois autres soirées, je tripotais la programmation. Si j'ai utilisé le moteur standard Spark AR Studio pour animer les ailes d'un papillon, la tâche de déplacer l'arrière-plan n'était pas si simple. Je ne comprenais toujours pas comment attacher non seulement un paramètre variable aux itérations du cycle de mouvement, mais une fonction de rappel à part entière. En tout cas, je n'ai pas pu le faire, les paramètres d'animation actuels ne voulaient pas y être transférés. Et une telle fonction est tout simplement nécessaire, car si dans le premier jeu (avec pizza) j'ai vérifié la collision par le fait d'ouvrir la bouche en y souscrivant, alors il n'y avait pas un tel événement ici. Et il fallait juste vérifier les collisions avec les objets environnementaux au fur et à mesure que le personnage se déplace, selon ses coordonnées actuelles. Et pour cela, les coordonnées doivent être comparées à chaque itération. Et puis j'ai pensé. Après tout, j'ai déjà écrit des jeux en javascript.Et pourquoi ne pas utiliser votre moteur d'animation que j'ai écrit plus tôt pour ces jeux? Son principe de fonctionnement est sensiblement le même: à un instant donné, le ou les paramètres changent entre les valeurs initiales et finales données. Et la valeur actuelle est transmise en tant que paramètre - vous ne le croirez pas - dans la fonction de rappel donnée, dans laquelle, par exemple, vous pouvez définir un objet 3D sur la scène à des coordonnées égales à ces valeurs actuelles, ou vérifier les collisions sur elles. Merci, Cap. J'ai dû adapter légèrement mon moteur à l '«écosystème» local: supprimer les références à l'objet fenêtre de là, car il n'est pas là, et d'autres petites choses.Et la valeur actuelle est transmise en tant que paramètre - vous ne le croirez pas - dans la fonction de rappel donnée, dans laquelle, par exemple, vous pouvez définir un objet 3D sur la scène à des coordonnées égales à ces valeurs actuelles, ou vérifier les collisions sur elles. Merci, Cap. J'ai dû adapter légèrement mon moteur à l '«écosystème» local: supprimer les références à l'objet fenêtre de là, car il n'est pas là, et d'autres petites choses.Et la valeur actuelle est transmise en tant que paramètre - vous ne le croirez pas - dans la fonction de rappel donnée, dans laquelle, par exemple, vous pouvez définir un objet 3D sur la scène à des coordonnées égales à ces valeurs actuelles, ou vérifier les collisions sur elles. Merci, Cap. J'ai dû adapter légèrement mon moteur à l '«écosystème» local: supprimer les références à l'objet fenêtre de là, car il n'est pas là, et d'autres petites choses.

Oui, encore - sur le défilement du paysage et des objets de l'environnement. J'ai décidé de mettre le monde entier dans un NullObject, c'est-à-dire dans un objet 3D vide, un conteneur, et de le déplacer en utilisant un seul paramètre pour l'animation - sa coordonnée x. À l'intérieur du conteneur, tous les modèles ont les mêmes coordonnées que s'ils étaient à l'extérieur, seulement maintenant pour eux, le système de référence est lié à cet objet vide. Les roches et les fleurs seront répétées (en outre, à différentes hauteurs du sol, conformément au diagramme de niveau), vous pouvez donc réutiliser ces objets lorsque vous vous déplacez, en leur donnant la position horizontale et verticale souhaitée à l'intérieur du "conteneur". J'ai écrit un système de recherche d'objets tombant dans le cadre (au décalage du conteneur actuel), qui définit l'objet qui laisse le cadre à une nouvelle position plus loin s'il devait y apparaître. Tu peux voir,comment cela fonctionne sur l'exemple de trois objets. (Il y en aura plus dans le jeu, donc vous ne verrez plus un tel effet de "réarrangement" des objets de l'environnement.)



La fonction de mise à jour des coordonnées des objets ressemble à ceci:

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

Le personnage principal


Le personnage principal du jeu est un papillon insubmersible, qui vole hardiment en avant, surmontant les obstacles. J'ai décidé de faire la gestion en fixant un marqueur, ou curseur (point lumineux), en tournant et en inclinant la tête. Et dans la direction de ce point, un papillon volera lentement (en fait, ce n'est pas un combattant, il ne peut pas non plus se téléporter). Par exemple, si vous vous abonnez à l'événement d'inclinaison de la tête, vous pouvez implémenter un contrôle vertical comme ceci:

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

De même pour l'horizontale. Seulement là, il est nécessaire de souscrire à l'événement non pas d'une inclinaison, mais d'une rotation de la tête (rotationY), et au lieu de la hauteur de l'écran, considérez sa largeur.

Colliders


Tout cela est merveilleux, le monde bouge et le personnage du jeu peut se déplacer librement sur l'écran. Mais maintenant, vous avez besoin d'un gestionnaire de collision, sinon aucun jeu ne fonctionnera. Il y a trois événements dans le jeu, selon lesquels la position du personnage peut changer. Il s'agit de la rotation et de l'inclinaison de la tête, ainsi que du mouvement du monde, dans lequel la coordonnée horizontale du joueur (x) augmente automatiquement.

Étant donné que je ne sais pas comment les itérations du gestionnaire de visage fonctionnent dans Spark AR - qu'elles soient appelées avec une certaine fréquence ou définies sur la fréquence d'horloge maximale possible, et dans mon moteur d'animation, je peux contrôler ce paramètre, j'ai décidé de définir des collisions dans ma fonction le mouvement du monde, qui est appelé à une fréquence que je fixe (60 images par seconde). Dans les événements de traitement de visage, nous n'accumulerons que les mouvements.

Le principe sera comme ça. Dans les événements d'inclinaison et de rotation de la tête, un décalage le long des axes x et y est accumulé. De plus, dans la fonction de défilement du monde, un déplacement horizontal s'ajoute également à la «tirelire». Et puis il est vérifié, si le déplacement accumulé est ajouté aux coordonnées d'origine, alors il y aura une collision avec l'un des objets du monde. Sinon, les coordonnées originales du joueur plus le décalage sont faites par les coordonnées actuelles. Et puis nous mettons à jour les sources vers les actuelles (réinitialiser) et réinitialisons les décalages. Si c'est le cas, ramenez les coordonnées à l'original. De plus, nous devons déterminer quel axe serait la collision, car les deux coordonnées ne peuvent pas être annulées. Sinon, le joueur «colle» simplement à un point dans l'espace et ne peut plus se déplacer nulle part. Il faut lui donner une liberté de mouvement le long de l'axe le long duquel la collision ne se produit pas.Vous pouvez donc lui donner une chance de contourner un obstacle.

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 fonction de détection de collision elle-même:

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

Pluie


J'ai utilisé le moteur de particules standard Spark AR Studio pour animer la pluie. Il n'y a rien de spécial ici. Nous ajoutons un objet Emtter à la scène, sans oublier de lui demander Emitter -> Space -> Local (au lieu de World). Il s'agit de s'assurer que les gouttes ne traînent pas dynamiquement derrière les nuages ​​pendant le mouvement, mais tombent toujours directement. Cette méthode est plus pratique pour déterminer plus facilement le moment où le papillon tombe sous la pluie - pas besoin de faire une correction pour la hauteur. Pour les gouttes elles-mêmes, j'ai préparé la texture appropriée. Et bien sûr, l'objet pluie se déplacera avec l'objet nuage. J'ai ensuite ajouté une condition d'accès au nuage au code de gestion des collisions. Et, s'il n'y a pas d'obstacle au-dessus du joueur en ce moment, alors le papillon tombe et le jeu se termine.

Modération


Pour une modération réussie, la vidéo obligatoire du jeu ne doit pas contenir de texte statique non lié à des objets. Sinon, le robot arrête instantanément la modération. Cependant, après cela, une personne vérifie le jeu pendant plusieurs jours. Et il n'aimait pas l'abondance de texte avant le match, ainsi qu'à la fin. J'ai dû réduire la quantité de texte. Après cela, le jeu a été approuvé.

Sommaire


Non pas que mon jeu ait été particulièrement excitant. Elle manque probablement de dynamique et de mécanique supplémentaire. Je publierai une mise à jour. Mais j'ai réalisé que faire des jeux sur Instagram est intéressant. C'est une sorte de défi. J'ai vraiment apprécié le processus de programmation et de résolution de toutes sortes de tâches sous le minimalisme en termes d'outils et de quantité de mémoire allouée. Je me souviens que quelqu'un a dit que 640 Ko suffisaient à tout le monde. Essayez maintenant d'adapter un jeu 3D de 2 Mo. Je ne dirai peut-être pas qu'ils sont suffisants pour tout le monde ... Mais essayez-le!


En conclusion, je voudrais rassembler dans une liste tous les moments non évidents que j'ai rencontrés. Peut-être que cela est utile à quelqu'un en tant que triche lors de la création de jeux pour Instagram.

  • Échelle. Ajustez l'échelle de tous les modèles 3D dans l'éditeur 3D afin qu'il soit immédiatement 1: 1 sur la scène du jeu.
  • . . , .
  • . FBX, . -. , .
  • . JPG , , , -, JPG, PNG.
  • . , , . Spark AR , . , , , 3D-.
  • 3D , : , . . .
  • , , . javascript .
  • Dans le contexte de Spark AR, il n'y a pas d'objet fenêtre et toutes sortes d'objets spécifiques au navigateur comme webkitRequestAnimationFrame, mozRequestAnimationFrame, etc. C'est un destin lors de la programmation en animation javascript.

All Articles