La grille, ultime frontière

Première étape cruciale dans l’évolution du système vers l’objectif HeroQuest : la grille (cf Tron Legacy ^^). L’évolution permettant l’ajout de murs et la gestion de toutes les conséquences induites, comme le pathfinder, le système de rendu, le raycasting, etc.

La solution est qu’une coordonnée de la grille, qui était un tableau d’Elements, devienne un GridBlock et c’est lui qui contient les Elements dans un de ses espaces. En gros GridBlock est lui même un tableau d’Element, un multi-tableau d’Element même, youhou bienvenue dans la 4ème dimension de la grille !

Évidemment ce n’est pas aussi basique ni aussi simple. GridBlock est volontairement une classe et non un simple tableau de plus. Ceci nous permet de prendre la partie du rendu globales des zones dans un ordre logique, de donner sur demande le contenu d’une zone et de prendre de son côté les fonctions d’insert et de retrait d’Element. Donc on rassemble ce qui peut l’être au lieu d’être au sein des méthodes ci-et-là.

On parle de zones, mais qu’est-ce donc ? En gros, nous voulons améliorer la gestion de l’empilement, et avec HeroQuest nous avons besoin de murs et portes, et pourquoi pas plus tard des indications au dessus des objets ou du héro. L’objet GridBlock a donc pour vocation de gérer des espaces virtuels pour faciliter le montage finale de la coordonnées. Il y a NESW, Center (centre), Top (dessus) et Bottom (dessous), ce qui correspond à un Ground en Bottom, un personnage ou arbre en Center, les murs et portes en NESW, etc.

Ensuite, il y a l’usage, car, dans un premier temps, j’avais tout mis dans la zone centrale, et fait en sorte que les méthodes liées utilisent la zone centrale par défaut. Ainsi, étape par étape, ça continue de fonctionner et ça permet d’isoler chaque étape de modifications.

En premier j’ai divisé la méthode qui construit actuellement la carte dynamiquement pour le POC, une grille de 10×10 qui ajoute aléatoirement des arbres. Et j’ai donc poussé le héro en zone centrale avec les arbres et le sol dans la zone du dessous. Rien de compliqué là dedans. Mais du coup le système de rendu n’affichait plus le sol (vu que c’est central par défaut).

Comme dit plus haut, l’avantage d’avoir GridBlock en tant qu’objet et non que comme simple conteneur, j’ai pu lui écrire une méthode draw() pour orchestrer le rendu par zone et donc déplacer le code initialement dans Iso vers GridBlock et ce pour chaque zone dans l’ordre désiré. Le tout en gardant les notions Z importante pour l’empilement. « Les » car Top se base sur Center, NESW et Center sur Bottom.

On a donc un caddie sur sol avec des arbres. Retour case départ, mais le caddie ne bouge plus.

C’est normal, le pathFinder se base sur un objet de type Ground et il cherche dans la zone centrale alors qu’on a déplacé dans bottom. Jusque là c’est simple de lui dire quoi faire pour corriger ça, mais est-ce au service de préparation de la grille du pathFinder de se tracasser de ça ? Non ! J’ai donc remplacé excludedTypes (types d’Element exclus du calcul) par une fonction que l’appelant implémente avec sa logique de tri.

Ainsi, un coup je peu être un personnage marchant sur des zones centrales inoccupée, autant une autre fois je peux être un oiseau parcourant les zones top. C’est bête mais important ! Du coup on a une fonction plus générique, une séparation logique propre et un résultat fonctionnel. Enfin, quand on oublie pas que le caddie occupe une place centrale et donc invalide sa position et l’empêche de bouger ^^ Bug connu précédemment revenu suite au changement de façons de procéder, excludedTypes était la solution précédente, on lui envoyait TreeElement à exclure ce qui incluait donc le PlayerElement, CQFD. Et grâce au fait que maintenant c’est géré côté appelant, donc dans IsoPlayer, il peut clairement indiquer ce qu’il veut ou non, et exclure de manière forte notre fameux PlayerElement.

Dernier détail, ce qui soulève en fait une optimisation, c’est le raycasting, donc le fait de trouver sur la carte ce que l’on survole avec le curseur de la souris. Le but est de déterminer la coordonnée survolée, et si un objet est sur la zone (et qu’il peut avoir une forme variable et donc un contour) le raycasting détermine si on est dessus ou sur une autre coordonnée, en gros.

Mais maintenant nous avons plusieurs zones, donc un travail plus long. Il faut donc prendre les zones peuplées et ne pas considérer Bottom. On simplifie tout en complexifiant le travail ^^.

En résumé, on a amélioré le système de grille avec une gestion spatiale structurée, adapté le système de rendu, de pathfinding et de raycasting, rassemblé du code et éclairci d’autres. Tout fonctionne à nouveau comme si de rien n’était mais nous ouvre à présent de nouvelles portes pour les objectifs désirés.

HeroQuest

HeroQuest est un jeu de plateau de chez MB de 1989 qui se joue de 2 à 5 joueurs. L’idée est simple, des héros affrontent des donjons, gagnent des trésors, combattent des monstres et obtiennent la gloire !

Plateau de jeu v1

Pourquoi est-ce qu’on parle de HeroQuest ? Pourquoi est-ce que je vous montre ça ? Tout simplement car le projet Nahyan stagne du fait de la distance entre le niveau actuel du moteur et ce que j’essaie d’atteindre. Et plutôt que de ne pas bouger et d’attendre la solution théorique optimale, je me penche sur un jeu simple que j’ai adoré et qui représente « l’étape d’après » de l’état actuel. Cela fait quand même quasi un mois que je n’ai plus bougé réellement.

Nous avons : une carte, des mouvements de caméra, un pathfinder, la possibilité d’interaction de base, une résolution de position au clic sur objet, un chef d’orchestre et une orientation lors du déplacement.

Il ne nous manque plus qu’une gestion de murs et de portes, un pathfinder adapté, un système de combat au tour par tour, une gestion de héro multiple (si on prend cette option), une mécanique de jeu sur des règles déjà existantes et éprouvées et un système de suivis de quêtes. On est pas loin :). Et je ne vous parle pas totalement du jeu en lui-même à intégrer…

La première étape va donc être de créer quelques éléments de design HeroQuest, de modifier le système de carte pour intégrer les murs et d’adapter le pathfinder. La découverte du donjon en se déplaçant pourrait être une seconde étape de départ.

Ensuite viendra le moment de gérer une carte complète (un donjon) et d’y ajouter, étape par étape, des objets, des monstres et des mécaniques de jeu.

Quand cela fonctionnera pour un niveau test, on pourra imaginer une mécanique de jeu globale avec choix du/des personnages avec leur fiche et inventaire et un scénario à suivre.

Certains me diront que le jeu a été réédité, que des extensions ont été ajoutées, que ça va tout compliquer, etc. c’est vrai. Mais il faut bien commencer quelque part :).

Mais avant de commencer cette première étape, nous allons faire un éditeur de donjon :).

Réflexion sur le sol et l’empilement

Une réflexion me taraude l’esprit et je vais tenter de l’exprimer ici. Il s’agit du sol de Nahyan, et donc de la mécanique TARS/Nahyan à mettre en place pour que cela fonctionne et ait du sens.

TARS empile les éléments et des méthodes, actuellement pas parfaites, gèrent l’insertion d’un objet ou son retrait sur base de son type. Suite aux observations faite lors des POC Caméra, on a noté qu’il fallait faire attention aux additifs, ces Elements décoratifs comme un curseur etc., qui disparaissent lors du reinit du subset n’étant pas sur la carte.

Ce point étant fait, dans Nahyan nous aimerions avoir un sol, ou de l’eau, et la possibilité de les parcourir. D’y avoir des objets, traversable ou non, comme de l’herbe ou une caisse, que l’on peut escalader ou non si on peut. Le seul facteur étant le temps de l’animation et du passage à la case suivante peut-être etc. Ne pas oublier que cela dépend des gestes et compétences connus. Avec cela il faudra ajouter les additifs.

J’imagine également que l’objet sol est interactif, par exemple on peut creuser le sol, pêcher dans l’eau, plonger/chercher dans l’eau etc. On ne parle pas encore ici de modification de terrain, bien que ça reste une idée intéressante mais difficile à tenir vu le mode graphique. Cependant, un minimum de terrassement me semble tout à fait possible. Enfin, ici on parlait surtout du côté interactif et conteneur de l’objet sol.

J’imagine donc que l’on garderait un type « Ground », qui resterait le plus bas niveau et l’unique Element de ce type de l’empilement et forcément en base de pile. viendrait par dessus les Elements décoratif traversable (herbes/buisson de cueillette) et additifs de sol (curseur). Enfin viendrait les objets qui s’empileraient.

En y pensant, si on dépose une caisse sur de l’eau, il faudra prévoir que l’eau détecte ce qu’il y a sur elle, et, fonction du poids et d’une sorte de flottabilité, l’engouffre (effet conteneur).

On imagine au final qu’il faudrait une méthode d’insert qui naturellement, en fonction du type de l’Element, l’ajouterait au bon endroit selon ces règles. Dans le cas de la caisse sur l’eau il faudra une animation pour symboliser la chute dans son conteneur.

Un mois de passé…

Une idée est apparue quand on a évoqué un autre projet intermédiaire : HeroQuest (voir article dédié). L’idée serait qu’en fait le sol soit en fait la coordonnée et que cet élément soit une division spatiale.

Il vous suffit d’imaginer un cube dont chaque face touche un autre cube. Ce qui vous fait 7 espaces exploitables, sans vouloir pousser le bouchon à manipuler des coins.

Ainsi vous avez le sol, l’espace exploitable sur-sol (là où le personnage se déplace), un espace au dessus ainsi que 4 espaces NESW à l’intérieur de la zone définie par le sol. Pour voir ça plus simplement imaginez une zone avec des murs appartenant à la coordonnées (sur le sol).

Voilà donc qui règle beaucoup de soucis. On garde les bonnes idées, empilement, calcul de la hauteur, placement de curseur et indicateurs, mais on réparti. Cela rend le système de rendu plus complexe, la gestion plus poussée, mais le tout me semble rendre plus de possibilités claire et moins de bricolage.

La technique n’est pas encore définie, seul le principe et ses tests théoriques sont validés, le reste ne saurait tarder.

Bon, sur ce, il est temps de sortir cet article.

Souriez vous êtes suivi

Il y a une chose que nous avons oublié avec nos tests miniatures, c’est le suivi de la caméra, son déplacement en fonction de la position du joueur. Le but étant de pouvoir se déplacer dans le monde et de garder la zone de visibilité du joueur à l’écran.

Il y a un tas de possibilités au sujet des caméras dans les jeux. J’en ai exploré deux. Suivre le joueur de manière centrée et déplacer la caméra si le joueur sort d’un cadre. Nous retiendrons que la seconde est beaucoup plus intéressante et agréable dans le cadre de Nahyan. On ajoutera à cela prochainement un centrage automatique sur joueur au bout de x secondes sans mouvement, pour être sur d’avoir la visibilité optimale.

Premier cas : suivi du joueur au centre

Il faut se rappeler que lors de l’initialisation de la Scene, une caméra est créée et que la vue est centrée sur la position du référentielle de la caméra. En gros on dit à la caméra que sa position de référence est x,y et on en déduit les offsets nécessaires pour le rendu.

Bouger le joueur/héro (mon caddie), ne déplace pas la caméra. Le lien entre les deux dépend du développeur de son projet utilisant TARS. On se positionnera donc dans l’événement d’update de la Scene, là où les Elements peuvent utiliser la méthode de déplacement (passée en paramètre pour rappel).

Là, l’idée est simple, si on change les coordonnées du joueur, on en profite pour calculer la différence entre la position initiale et la destination et on l’impacte aux offsets de la Caméra. De but en blanc ça donne un déplacement bloc par bloc très brutal à regarder.

Le déplacement se faisant, une chose oubliée alors sera le refresh du subset de la map, car sinon rien ne change visuellement, on ne fait que déplacer la map dans la direction du joueur, sans masquer/ajouter les éléments qui devraient apparaître ou disparaître. Pour se faire il faut changer le référentiel de la caméra en pointant la position du joueur et ensuite de demander le réinit du subset de la map. Ceci recrée également le subset du pathfinder (je vous laisse deviner l’importance de tout ceci).

Second cas : suivi du joueur hors cadre

On parlait d’initialisation de la caméra dans le premier cas, il faut aussi garder en mémoire le redimensionnement de l’écran (navigateur), qui change les données pour le rendu, les subsets etc. On va avoir besoin de s’accrocher à cet événement dans IsoPlayer pour fabriquer un cadre virtuel à l’intérieur de la zone de rendu. Un cadre dans un cadre.

Grâce à cet événement on récupère la taille à jour de l’écran, on peut donc créer 2 points A et B, symbolisant le point haut gauche et bas droite du cadre. Idéalement ça serait du 25% de tous côtés, mais on a découvert que l’élévation en Z des blocs change l’impression visuelle, on a donc corrigé en haut 40%, gauche 30%, droite 30%, bas 30%. En gros le point A commence à 30% du bord gauche et 40% du bord supérieur.

Représentation factice du cadre

Ensuite, on en revient à l’événement d’update de la Scene, mais au lieu de se placer dans la fonction de déplacement du joueur on se place sous sa fonction d’update, ainsi on récupère les données à jour du joueur. L’idée est de détecter s’il est hors du cadre, et de combien et d’affecter les offsets de la Caméra de ces valeurs.

Le fait d’être hors de la fonction de déplacement nous permet de demander la position du jouer AVEC son delta de déplacement, ce qui va nous permettre d’avoir une glissage d’écran fluide, et non saccadée comme le premier cas.

Il nous reste ensuite à faire le rafraîchissement du subset. MAIS, comme on a changé l’idée du déplacement, avec le cadre, garder le joueur comme point référentiel est une erreur qui va vous faire de sales emmerdes si vous ne faites pas attention au pourquoi du comment. Le référentiel est en fait maintenant le centre de l’écran.

Réfléxions globales

Les valeurs modifiant les offsets et le point référentiel de la Caméra sont donc les 2 piliers du suivi caméra.

Les calculs nécessaires se font à chaque passage d’update, il faut donc porter une attention particulière à l’optimisation; ne recréer le subset que si nous avons bougé; n’inspecter le déplacement que si nous sommes le joueur (dans notre cas); ne pas tester des directions de déplacement si son opposée est déjà validée; etc.

Une autre observation globale est que le curseur et target (anim01) sont ajouté avec le joueur après l’init du subset. Car ils ne font pas partie de la map, ce sont des additifs. Le fait de déplacer la caméra efface donc le curseur et target du rendu. Cette observation sera valable pour tous les additifs visuels, il faudra donc les prendre en considération au moment du réinit du subset par IsoPlayer, qui ajoute déjà le joueur.

L’aiguillage de ton titre

Je me suis recréé un poste de travail sur mon PC principal, ce qui m’a demandé de réinstaller toute la panoplie de dépendances, logiciel, etc. De plus j’ai mis à jour le package.json ce qui a engendré quelques soucis, du coup nettoyage, un tslint réveillé et quelques commits plus tard : une solution fonctionnelle, à nouveau ^^.

Et donc, quelles sont les nouvelles ? J’ai écris l’écran titre, basé directement sur Scene, bien suffisant, et sur un Sprite (et non un Element), c’est là que certaines choses apparaissent comme « au mauvais endroit », trop spécifiques pour être dans TARS, à diviser ou à revoir, la liste n’est pas petite mais ce sont les aléas des POC continus j’imagine.

Cet écran titre est donc la Scene de démarrage, fait apparaître ‘daaboo’ progressivement, attend 2 secondes puis fondu au noir. Il restait ensuite à définir ce qui se passe ensuite, le fameux aiguillage dont j’ai déjà parlé.

L’aiguillage a pris la forme d’un service chef d’orchestre (Conductor) et reçoit les demandes de changements de Scene. Actuellement, on démet et on ajoute à la suite une Scene nommée. Ce n’est pas assez, mal nommé et à revoir, mais après avoir tergiversé une heure sans trouver un angle d’attaque à ce sujet complexe, j’ai décidé de démarrer quelque part et ça fonctionne. Cependant il faut rester clair : c’est à revoir.

C’est la Scene titre qui déclare, une fois arrivé au fondu au noir, la demande de changement de Scene et c’est elle qui sait vers qui elle veut nous rediriger. Ceci démontre bien que cette séquence fait partie du « jeu » et non de TARS. En gros, Conductor reçoit la demande et émet un événement que Game reçoit en s’étant abonné dès le début, ainsi il peut modifier les Scenes actives.

Ceci permet aussi à d’autres Scene qui en auraient l’usage, de s’abonner et de réagir selon les changements. On imagine facilement un HUD se modifier en fonction de la Scene de rendu ou un ensemble de Scene composant un rendu en couche qui auraient besoin de savoir ce qui se passe. Ceci n’est pas un canal de communication (développement spécifique au projet) mais une gestion propre à TARS concernant les Scenes.

Pour étayer ce qui ne va pas dans Conductor il faut s’imaginer une Scene de départ et une Scene à venir, le tout en gardant en tête que chaque Scene a une transition d’arrivée et de départ. Du coup si vous voulez quitter la Scene de départ pour aller vers la suivante, vous déclenchez en fait sa transition de sortie, elle n’est donc pas encore inactive et à retirer de la liste des Scenes actives. C’est cette Scene de départ qui doit en fait se gérer et quand elle a fini, lancer la transition vers la Scene suivante.

Il faut aussi garder en tête que le loader s’affichera si la Scene suivante n’est pas chargée au moment de l’activer et ce, sans transition. Donc pour faire de belles transitions, il faut que les Scenes soient chargées à l’avance ou en tâche de fond, c’est à dire charger la Scene sans la mettre dans les actives et garder le lien quelque part, le récupérer dans une Scene peut-être (ce qui n’existe pas, mais pas bête de le prévoir).

Enfin, une Scene peut s’ajouter par devant ou par dessous une autre, permettant telle ou telle transition ou effet de passage d’une à l’autre. S’ajouter à plusieurs peut-être et retirer de la même manière. Et est-ce utile de penser un remplacement ou déplacement de Scene(s) ? Beaucoup de questions et de gymnastique de code pour gérer l’ensemble.


La suite ?

Conductor sera modifié au cas par cas, jusqu’à remplir tous ses objectifs au mieux. Rien ne sert de se concentrer dessus absolument vu sa complexité.

TARS en lui-même a une belle liste de TODOs (~22) et comme dit ci-avant, certains morceaux doivent en sortir, ce qui ne va pas se faire sans mal.

Du coup, le meilleur moyen d’avancer, même sans serveur/back actuellement, est d’avancer sur le POC d’usage et donc « Nahyan », enfin, un semblant. Je pense continuer d’explorer l’interaction qui sera quand même la base du projet. J’ai émis quelques idées sur le comment mais je vais en faire un article à part entier.

Il reste également l’éditeur comme sujet assez imposant. Là encore, est-ce du TARS ou un outil de jeu. Les types d’Element étant un problème soulevé à sortir de TARS, c’est encore un beau débat.

Il y a de quoi faire c’est certain. Affaires à suivre… 🙂

Comitards 3 : Folk·o·pedia

Cela fait presque 2 ans que c’est dans les cartons, et si on remonte à Comitards 2.3, bien plus encore. Ceci dit, c’est une attente justifiée vis à vis de l’important changement que cela cache. Évidemment, avec le hack du serveur et la perte d’articles, les derniers articles datant de la V2 et de Badawok, le blog manque d’actualité. On va corriger ça avec cet article.

L’idée de base est de pouvoir ajouter plus de types de contenu, ensuite de ne plus devoir modifier le site pour y arriver, et enfin, car je suis seul, que la communauté arrive à s’auto-gérer. En complément, tant qu’à faire, élargir/revoir l’idée de Comitards, profiter du retour d’expérience de l’idée et du site, et ainsi reforger le site entier.

Déjà le constat, le nom « comitard » est souvent mal compris, « Je ne suis pas comitard », et dans d’autres pays cela nécessite une explication. Du coup j’ai émis quelques idées et proposé une sélection à quelques proches concernés. Nous accueillons bien plus que des « comitards », il suffit de regarder les ordres en tous genres. Il s’agit en fait de concerner les acteurs et groupes des traditions, souvent festives. Le folklore dans son ensemble.

Folkopedia a été retenu, voyez y la contraction de folklore et d’encyclopédie, dit folk-o-pedia. Le ‘o’ me fait sourire, comme dans les imaginaires éthyl·o·trons, truc-o-matic, etc. qui contractionnent les mots entre-eux faisant naître un néologisme d’association.

La mécanique du site est également complètement mise à terre. Il ne s’agit pas de simplifier le schéma DB comme la dernière fois ou de faire le grand nettoyage en ajoutant ce qui manquait depuis la dernière fois, mais de repenser intégralement le système de base, et là j’ai été chercher loin.

Une de mes premières missions en tant que consultant au Luxembourg (vers 2011) m’a amené à découvrir les ontologies au travers d’une application dans le domaine de la recherche sur les tests assistés par ordinateurs (TAO/OAT). Très nébuleux pour moi, il m’aura fallu quelques recherches personnelles et un intérêt sur les métadonnées, avec un cours en prime, pour faire le lien avec Comitards. Évidemment pas au premier coup d’oeil.

Notez que j’avais tenté de normaliser le contenu de Comitards via RDF avec le projet FGPI : Folk Groups and People Involved vocabulary. Ceci, par manque de connaissance et de capacité de contrôle n’ira pas plus loin et restera probablement un brouillon intéressant sur un sujet ô combien difficile.

Ceci dit, je me base sur l’idée et non pas une application stricte, car les performances ne sont pas au rendez-vous et j’avais envie d’intégrer un cadre et des définitions pour le concept général. Du coup je repars sur une base de données relationnelles, un schéma spécifique et une optimisation par extraction des données. On aura un cache de lecture simplifié tandis que l’écriture se fera dans le système étalé. Du moins c’est comme ça que je vois la chose. Flexibilité totale et optimisation.

Pour ceux que ça intéresse, prenez toujours en compte l’usage. Combien vont écrire pendant que combien vont lire, que ça soit concurrent ou non. Sur un site comme Comitards, on a très peu d’écriture et beaucoup de lecture. Vous savez donc ce qu’il faut optimiser et pourquoi.

Avec ce nouveau schéma, plus besoin de modifier le site pour ajouter du contenu. Il suffit d’entrer les données du nouveau sujet, ses attributs et les liens possibles. Le tout sera fait via une zone d’admin (j’espère 🙂 ), mais ça reste l’idée.

Pour développer, un « quelque chose » (une photo, une personne, un groupe folklorique, …) est une entité, et celle-ci a des attributs (nom, date de création, histoire, …) et des liens vers d’autres entités (photos, documents, …). Le plus dur là dedans est la définition de chaque chose.

L’ensemble de cette réflexion est basée sur un cahier des charges regroupant tout ce que l’on souhaite intégrer, avoir une vue la plus claire possible du projet, des besoins, optionnels ou non. Ensuite, cela a été soumis à des volontaires de tous horizons, raffinant le premier jet jusqu’à obtenir quelque chose de correspondant à ces cibles. Merci à eux.

Dans cette continuité, merci mon Gros Lézard (Lord Suprachris), pour la belle prise de tête sur la conception du schéma de la base de données. Cela a été périlleux mais on a réussi à faire correspondre tout le cahier des charges en un seul schéma ! Et ça c’est pas rien !

Mais les prises de têtes ne sont pas finies, il y a encore un autre point important que je pensais définit depuis le début : le stack technologique. J’avais oublié un détail : je suis seul. J’avais imaginé un stack très développé, beaucoup de challenges et d’apprentissages, mais avec de telles envies, ce n’est pas demain que le site sortira.

C’est ma compagne, Boudine pour ne pas la citer, qui m’a remis les idées claires avec une réflexion sur le temps déjà passé, la difficulté que je me suis donné et l’inatteignable objectif. Du coup, à contre coeur, mais en sachant le bien fondé, j’ai revu le stack, du moins j’essaye. Il y a beaucoup de niveaux à décortiquer, mais je vais y arriver.

Enfin, car on arrive quand même au bout de l’état des lieux, il y a le design, lui aussi confiant dans les premières maquettes, mais aujourd’hui plus vraiment. Il faut attraper l’utilisateur dans une mécanique fort complexe et rendre ça sexy. Qu’il prenne plaisir à contribuer et à consulter. C’est chaud. Là aussi j’ai sollicité des avis, mais j’ai perdu mon auditoire tant ils se bloquent sur la complexité du projet. Je ne baisse pas les bras, je finirait bien par trouver cette nouvelle interface moderne et flexible capable de répondre aux attentes de Folklopedia.

C’est un peu comme dans Social Network (film de l’histoire de Facebook), j’en garde une phrase intéressante : « On ne sait pas encore ce que c’est ». On sait très bien ce qu’on a développé, bien sur, mais pas forcément ce que ça va ou peut devenir, son potentiel, ses usages et dérives en tous genres.

On en est là, entre une fin de post analyse et de début des travaux, avec un objectif de base débuté avant fin d’année. On avancera entité par entité, outil par outil, il y aura des refontes, des erreurs, des cris et des larmes, mais il y aura surtout un résultat à cette aventure complètement folle :

Numériser les traditions, enregistrer les vécus, préserver l’éphémère : L’encyclopédie folklorique.

Le solitaire

Ceci est l’histoire d’un programme qui hantait mes pensées et mes rêves depuis presque 20 ans, tenté par plusieurs langages et labourant mes méninges de mille façons… Son nom résonnera longuement dans tous les méandres d’Internet… Écoutez ! Il se nomme Nahyan !

Derrière ce titre se cache un clin d’œil à la saga Axle Munshine, le Vagabon des Limbes, découvert grâce à mon père. Plus particulièrement au numéro 22, éponyme de cet article. Car ce que vous ne savez pas, c’est ce qui est arrivé à TARS !

Mais quel est le foutu rapport ? Tout simplement une division, la première entre ce qu’est TARS : un framework; et Nahyan : le projet. Destinés à vivre en osmose mais clairement séparés. D’où le clin d’œil à « Le solitaire », où 2 personnes s’aimant fusionne un peu trop…

C’est le constat de départ en voulant ajouter un écran titre, éprouver le loader et ainsi suivre le plan de l’article précédent. Les 2 ont fusionné un peu trop : POC et framework.

« Bah c’est simple suffit de les séparer… » Et une baffe, t’en veux une ?

89 modifications, des jours et quelques fixes plus tard, l’ensemble fonctionne à nouveau, mais non sans mal. Voyons en gros ce qui a changé.

D’abord le code de TARS est déplacé dans un répertoire à lui tout seul au sein de l’app, tel une dépendance. Ainsi quelques classes ont été sorties comme DataProvider et les implémentations d’IsoScene.

Ensuite il a fallu rebrancher ce qui ne fonctionnait plus, ou ne devrait plus de la manière actuelle, au vu de la séparation des mondes. Clairement le DataProvider, une variable d’environnement et quelques liens et broutilles annexes.

Concernant le DataProvider c’est intéressant d’en parler un peu plus, car avant on étendait la classe de TARS comme modèle à suivre pour être compris du framework, ainsi le système d’injection savait la récupérer, mais vous ne pouvez pas le faire si le framework ne connait pas votre classe externe (logique) du coup il a fallu trouver une autre solution qui ne casse pas tout le code et reste logique.

Ainsi le DataProvider dans TARS devient un conteneur, le modèle devient une interface (ça reste une obligation à suivre) donc le conteneur ne peut recevoir qu’une classe qui suit le minimum requis par l’interface sans vous empêcher de faire des rajoutes pour votre développement. Ainsi on accède au DataProvider réel en le demandant avant de le toucher.

this.dataProvider.loadCursors()

Devient :

this.dataProvider.get().loadCursors()

Ce n’est peut-être pas la bonne manière ou la manière définitive, mais ça fonctionne et le POC va pouvoir se poursuivre. Ainsi on repart sur le plan précédent avec un écran titre et le système d’aiguillage, développé et testé en même temps grâce à cette division.


Autre point important, les Scenes qui étaient injectées dans Game via un fichier de configuration. Et ben ça c’est du passé, en tout cas dans TARS. C’est à l’utilisateur du framework de faire un appel à Game.load() en lui passant les scènes avec le nom de celle qui démarre et de celle qui représente le chargement. Ceci sera à voir quand l’aiguillage apparaîtra.

Cette injection manuelle se fait dans le component, ce qui réaffirme que le component fait partie du projet et non de TARS lui-même. Ceci permet aussi d’injecter ses propre classes héritant de telle ou telle déclinaison de Scene. Tars s’occupera de ce qu’il connait et vous pourrez ajouter et gérer votre niveau à votre aise sans vous tracasser d’avantage.

Rien ne vous empêche de continuer à utiliser la factory des scènes qu’on a retiré ou d’utiliser un fichier de configuration pour votre projet si vous le jugez nécessaire, mais ceci n’entre pas dans TARS.


Tout ceci me ramène à mes développements de Badawok. Cette séparation ramène de très bon souvenirs de challenges de construction de framework et relance les réflexions et les possibilités du projet TARS. Petite bouffée d’oxygène une fois accompli, un Mars (erk) et ça repart !

Tractopelle de cerveau

Seconde partie de la capillotractorisation pour résumer le second mois écoulé. Une fois le déplacement et ses différentes techniques en place, un nouvel objectif a été décidé : l’écran de chargement. Ça parait con, mais pas du tout !

En gros, il faut charger une scène de chargement avant le reste et l’utiliser quand les autres ne sont pas prêtes. Jusque là, simple en théorie. On ajoute donc une scène dans le fichier de conf du jeu, on modifie Game pour le chargement et … écran blanc !

Mais cette fois il ne m’aura fallu que 5 jours de réflexion pour trouver que c’est le même problème que notre précédent soucis d’Observable vs EventEmitter. Ceci dans une autre mesure car cela touche toutes les couches : Game > … > Element.

Du coup, remplacement des fonctions load() , retrait de l’Observable, ajout de l’event, rebranchement, et toujours pas bon. La manière de charger Element manquait un cas, non détecté car un chargement se passe vite et cela n’a jamais déclenché de soucis. En gros, un Element sur demande est chargé et surveillé mais du moment qu’il est chargé l’event onLoaded (alerte de fin de chargement) n’est pas émit. Ça ne peut donc pas fonctionner, logique.

Une fois le tout rebranché, vérifié et contrôlé à nouveau, ça fonctionne. On a donc un système qui charge un loader sans rien afficher, affiche le loader et charge le reste. Le loader est retiré quand tout le monde qui doit être actif est prêt. Actuellement le loader est un IsoScene qui utilise les mêmes Sprites donc à peine il apparait qu’il disparaît. Du coup prochaine étape, un écran de titre pour tester plus avant le loader et installer un aiguilleur de scènes.


Autre point majeur qui a nécessité et nécessite encore des modifications, j’ai ajouté un éditeur, ce qui impacte 2 choses : un Component pour le routage et une Scene pour le comportement.

Ceci a demandé de diviser, pour mieux régner, le composant Iso en 2  : Editor et Player. Mais va demander de créer une couche Viewer pour parfaire les possibilités. En gros la différence se joue sur les éléments réactifs nécessaires, cursor, target, hero et la surcharge et comportements préparés au niveau des events souris (pour le moment).

C’est là qu’un petit défit apparaît, la fonction load signale la fin du load, mais dans le cas d’un héritage (Iso > IsoPlayer), la classe player aura aussi  une fonction load qui appellera celle du parent, du coup 2 fonctions qui signalent la même fin. Sauf que si vous êtes une classe player vous ne voulez pas que le parent prévienne, c’est votre rôle. Comme on a retiré les Observables au profit d’EventEmitter, on doit trouver une solution différente.

Et pourquoi pas un retour aux sources ? On passe à la fonction load une fonction de callback. Si présent on l’appel, si non présent on signal la fin du load. Allez pour une fois je vous montre un extrait de code.

// Classe mère Scene
public load(callback?: Function) {
  // Need to be overrided
  if (callback) {
    callback();
  } else {
    this.onLoaded.emit(true);
  }
}

// Classe fille Iso
public load(callback?: Function) {
  super.load(() => {
    ... du code ...
    ... une condition de fin ...
      if (callback) {
        callback();
      } else {
        // Alert that we are loaded
        this.onLoaded.emit(true);
      }
    ... fin de la condition de fin ...
  });
}

// L'appel dans Game
this.activeScenes.forEach((s: Scene) => {
  s.onLoaded.subscribe((sceneLoadResult) => {});
  s.load();
});

En gros si on appel s.load() sans passer de call back, la classe appelée est le dernier niveau, et elle appellera ses parents dans l’ordre avec la même logique. Du coup player se charge (sans callback), se passe à son parent (Iso) sous forme de callback, le parent (Scene) fait de même. Du coup Scene appelle le callback de Iso et l’exécute, au moment de vérifier la fin, il y a le callback de player a exécuter, qui à son tour se termine et au moment de vérifier la fin, pas de callback donc on peut enfin signaler la fin du load() via l’event onLoaded.

Et ça fonctionne ! Plutôt pas mal même, tout en maîtrisant le chaînage des appels et en prévoyant à chaque niveau la surcharge possible, ce qui rend l’ensemble flexible et extensible. Il ne faut pas oublier que TARS est un framework et que Nahyan étendra les classes proposées pour ajouter les comportements désirés spécifiques. Player par rapport est déjà une surcouche peut-être trop spécifique mais rien n’empêche le développeur de partir sur base de Iso et non de player s’il se sent l’âme de coder ce qui lui faut.

On a donc un ensemble fonctionnel de structures à finir (viewer) et il reste à progresser sur l’éditeur.


La suite ?

L’éditeur pour manipuler map ainsi qu’un écran titre pour l’aiguillage entre scènes. Ensuite il faudra sûrement revoir la structure pour séparer framework d’implémentation.

On pourra aussi poursuivre côté interaction du héro à destination (arbre) en faisant attention à la séparation framework/usage, sûrement avec un POC, et donc un sytème d’interaction (fenêtre. écran conditionnel, …).

Il y a aussi un point qui est embêtant pour le moment c’est la capture vidéo du canvas, mais des personnes ont développé des solutions d’extraction à partir du canvas pour écrire un gif, le tout via le browser, ce qui est assez costaud. C’est assez complexe et j’aimerai faire quelque chose de simple, donc on verra si usage ou non de lib externe et possible solution simple interne. Sinon en parallèle, je pensai mettre un espace démo qui serait mis à jour en fonction des articles pour l’illustrer.

Évidemment il y a aussi la partie serveur qui reste fort obscure pour le moment et dont un des points est la capacité de faire le temps réel et donc du push ou un socket, ce qui semble préférable. Je me base pas mal sur l’expérience de BrowserQuest, qui se base sur un back nodeJs pour WebSockets, mais je n’ai aucune expérience de node et que de la théorie sur webSocket, ça risque d’être chaud. À la base, je voulais faire le back en PHP, ma référence précédente, il y a Ratchet, à voir qui me conviendra le mieux.

Au final, le socket n’est là que pour rapatrier l’information à jour et la plus légère possible. C’est l’action de l’utilisateur qui envoie la mise à jour et un serveur quelconque qui s’occupe des impacts.

Imaginer un système ou les joueurs parlent et bougent est simple, mais du moment qu’il y a projectiles, déplacement en combat, déplacement d’objet ou altération de la carte, là ça devient plus compliqué. Mais ce n’est qu’un challenge de plus à affronter après tout :). Le tout est de bien comprendre comment découper l’ensemble et que chacun fasse son travail.

Affaire à suivre donc 🙂

Capillotractorisation

Nous voilà quasi 2 mois plus tard, et non je n’ai pas abandonné ! Non je ne suis pas passé à autre chose (pour changer…) ! Je suis resté coincer +2 semaines sur un soucis et 5 jours sur un autre. Détaillons ça ensemble.

Premièrement, j’ai suivi le plan annoncé, mais pour ce faire j’ai dû introduire la notion de direction à Element et donc comment dessiner un même objet dans une direction différente ? Après plusieurs tergiversations, l’idée est de faire 4 images arrangées de la même manière pour préserver le mappage d’animation et on les nommera par leur direction.

Ensuite il faut une technique pour dire à Sprite l’usage et la manière dont il peut s’en servir. Pour faire générique j’ai appelé ça des Layers (calques), ainsi on a la longue chaîne :

Game > Scene > Element > Sprite > Layer > Image

Ceci donc rajoute une couche d’Observable dans la longue chaîne et pouf… écran blanc, chargement complet sans erreur et rien de plus…

Il m’aura fallu +2 semaines d’expérimentation pour déterminer que les Observables étaient la cause et que la solution était de les supprimer et remplacer par des EventEmitter. Et besoin de rien changer d’autre ! Car compatible, excepté la division de l’action appelée et du listener en gros (oh zut, une ligne de code en plus…).

Ceci fait tout fonctionne comme avant, à ceci près qu’on a des Layers en plus. Du coup j’ai pu avancer sur la partie pathfinder et déplacement de notre héro le caddie.

Caddie, ce héro

Ce caddie, récupéré d’un POC Auchan raconté dans un article précédent, a déjà les 4 images nécessaires donc allons y. J’ai modifié les fichiers de configurations, les interfaces, les chargements et tout le nécessaire. Il ne reste plus qu’à le faire bouger !

Commençons par le pathfinder, le calcul entre la coordonnée du clic et la coordonnée actuelle du héro. Car sans chemin à suivre, pas de déplacement ! Ainsi en ayant décortiqué la mécanique des différents pathfinder j’ai développé une petite solution sympa qui nous donne en un temps record directement le chemin final (si trouvé) et grâce à un petit raccourci (en mode throw exception à travers des boucles) on gagne pas mal de temps.

Pour les curieux, un pathfinder simple s’occupe de partir de la coordonnée de destination et de se propager de case en case jusqu’à ce qu’on trouve le héro. Il suffit de remonter le chemin à l’envers. Si on parcours tout sans succès c’est donc injoignable, ou on est hors carte. Ceci fonctionne avec tous systèmes de grille. Il ne faut pas oublier de préparer la grille de coordonnées utilisables, car on ne peut pas se déplacer sur un arbre ;).

Ensuite, ça devient « comique ». Nous avions déjà une animation avec le Target (vagues bleues), mais là il s’agit de déplacer physiquement un objet d’une case à une autre. Cependant, nous avons comme coordonnée un nombre entier ([1,1], [2,5], etc.). J’ai donc ajouté un delta lors du draw() qui déplace le point de référence et donne l’impression qu’il se déplace. Évidemment j’ai ajouté dans l’update() un petit calcul qui détermine un déplacement dans un laps de temps défini et par une règle de trois on sait à quel pourcentage du chemin (entre deux cases) il se trouve.

C’est là qu’on oublie un détail, il est toujours attaché à sa coordonnée, seul le delta change. Et donc il passe sous les blocs dessinés ensuite… logique… :(. Il ne faut donc pas oublier de changer de « parent » l’objet qui se déplace au moment d’atteindre sa destination. Mais ce n’est pas suffisant. On a un caddie qui disparaît (si coordonnées supérieures), sous le bloc durant son déplacement puis réapparaît soudainement, on a un clignotement désagréable.

Pour solutionner ce clignotement et parce qu’on a décidé d’avoir une 3ème dimension dans le sol (l’élévation), la solution a été de changer de parent à mi-chemin, ainsi on réduit la gêne et l’effet de clignotement. Le tout en bénéficiant d’un effet favorable puisque l’objet en déplacement s’adapte en Z lors du changement de « parent », ce qui ne se faisait pas avant. On a donc l’impression que le caddie monte/descend bien les « marches ».

Ensuite vient la rotation, ce qui a été rapide à solutionner. Prenez la coordonnée actuelle et la suivante et vous pouvez calculer l’orientation. Là on adapte alors la valeur d’orientation de l’objet et le caddie tourne.

Encore plus fort et sans les mains, si je clique sur l’arbre, il ne se passe rien, mais si c’était un coffre j’aurais envie que le caddie aille devant, bien orienté et interagisse avec. Pour ce faire on a besoin d’une notion de zone d’accès. J’utilise le système d’orientation NESW car l’objet peut pivoter aussi. Donc si mon arbre est orienté à l’Est, L’Est devient son Nord. Là ça devient compliqué dans la tête.

Du coup, quand je clique sur l’arbre, la coordonnée est refusée, on cherche le premier élément sur le sol et on lui demande ses zones d’accès s’il en a, sans oublier de réorienter en fonction de l’orientation de l’objet lui même. Ensuite on les teste toutes, et si elles ne sont pas occupées, on comparera la distance entre chaque et on gardera la plus rapide. Évidement, cas réel, si on est déjà dessus, on zappe directement et on passe à l’interaction.

On a donc un système de déplacement agréable et fonctionnel. Une kyrielle de bugs a été corrigée en même temps, comme la duplication du caddie pendant son déplacement, le resize qui faisait de même (mais autre raison), la perte du caddie en déplacement etc… Je ne me rappelle plus de tous les cas corrigés. Le code a bien été peaufiné. Même si, au final, vous ne voyez pas beaucoup de nouveauté.

Pour ne pas vous prendre trop la tête en une fois je vais diviser ce compte rendu en deux. 🙂 La suite n’en est pas moins capillotractée.

Carrêmisation

Qu’est-ce que la « carrêmisation » ?

Et ben rien du tout 🙂 C’est l’optimisation qui fait faire un régime sec à notre système de raycasting, en évitant dès que possible le travail inutile. Ceci se fait de plusieurs façons.

La première consiste à commencer par le haut. Car les chances sont plus grande qu’un objet sur un autre recouvre celui-ci. Là aussi il y a deux points à regarder, d’un côté la Scene qui contacte ses Elements un par un dans son sens inverse de rendu (dans le cas présent de la Scene Iso – isomérique) et d’un autre l’Element lui même qui possède potentiellement plusieurs Sprites, dès lors Element générique les empiles et donc le contrôle se fait en commençant par le haut.

Ensuite, même si déjà fait dans mon code précédent, on peut épargner des contrôles de bounds si au moins un a été touché par le point.

Vous remarquerez que le contour du tronc n’apparaît plus en noir.

La seconde, consiste lors du test d’un Sprite de détecter s’il a des bounds (bords pour la détection) et ensuite, s’il en a, une fois les calculs de repositionnement effectué, on test si le curseur est au minimum dans le cadre virtuel de l’image du Sprite.

Le tout combiné, entre les optimisations côté Iso et Element, nous donne ceci :

On remarque que seuls les Elements qui sont susceptibles d’être en contact avec le point ont été marqués, ce qui signifie que le calcul n’a été fait complètement que pour ceux-là. Vous imaginez donc, entre la première photo de cet article et cette dernière, l’avantage directe de cette optimisation.