Folk·o·pedia top départ

Suite de l’article précédent sur le même sujet, mais cette fois de manière plus technique. Nous voilà déjà 7 mois plus tard… Rien n’a été fait si ce n’est ce mois de Mai, du coup décortiquons.

Comme dit précédemment, c’est un gros projet, ambitieux et novateurs à différentes échelles, et donc accompagnés de complexités et difficultés pour le démarrer/réaliser. Oui OK je me cherche quelques excuses mais elles sont véritables.

« Mais tout vient à point à qui sait attendre » un sage aurait-il dit; et c’est effectivement le cas : l’illumination du premier jalon. Je vais donc vous parler de ce qui porte le nom, actuellement, de Schema Definer ou le définisseur de schéma.

« Mais qu’est-ce donc ? » demande la foule tel un 42 en réponse d’une longue attente. Tout simplement notre première étape, car vous n’imaginiez quand même pas que le site pouvait se gérer sans structure ?

Aussi permissif le site, en objectif, sera; autant il lui faut une mécanique de définitions pour vous proposer celle-ci. Quelle belle phrase.

Récapitulons après ce blabla. Jusque là nous avons défini un schéma de base de données, donc un moyen de stocker de l’informations organisées selon une définition. Cette dernière décrit un ensemble de tables représentant notre infinie possibilité à décrire tout système folklorique.

Ça c’était il y a +2 ans, selon les idées sur les ontologies. Ensuite j’ai cherché le stack ultime du web de demain… Ok sans succès, on va partir sur un Angular/Lumen pour débuter. Mais je ne désespère pas de trouver le design du web de demain quand on attaquera le front (partie visible et utilisable).

Déjà le fait d’avoir simplifié et décidé du stack, ça soulage, beaucoup même. Je vous recommande Homestead via Vagrant pour démarrer avec simplicité.

Revenons à nos moutons après cette digression. On a de quoi stocker de l’information brute qui n’aura du sens que si on lui en donne, c’est le rôle du Schema Definer. Le schéma sémantique sur le schéma structurelle (stockage).

Si je stock 36, rien ne me dit que c’est mon age, sauf si une définition l’indique au système.

Là je vous ai perdu, entre logique effrayante et explications à rallonge.

Comme dit nous avons un système de stockage différent de ce que l’on a habituellement (relationnel, non relationnel structuré, …) où la conception contient le sens des données. Mais nous, non. Notre mécanique retiendra des nombres et chaînes de textes reliées entre elles, de manière relationnelle.

OK, c’est quoi ce schéma ? Tout simplement la définition de ce que l’on veut encoder sur Folkopedia : une personne, un groupe, un document, …

Pour faire simple, en expliquant sommairement la mécanique triplet RDF, vous aurez un objet/entité (virtuel), qui a des propriétés avec des valeurs.

De manière appliquée : je suis l’instance d’une Personne, ayant pour surnom : Killan. Le schéma précise qu’il existe une entité Personne, avec pour propriété possible un surnom qui sera de valeur texte.

La valeur pourrait être la référence d’une autre entité. Genre une entité livre peut avoir une propriété auteur qui a comme valeur la référence d’une entité personne. Etc.

Avec une table de 3 colonnes vous avez votre système, simple mais horriblement pas performant. Et on ne va pas entrer dans ces histoires-là ici. Le schéma Folkopedia est différent tout en suivant le principe et un futur système de cache fera le delta de performances. On reviendra dessus bien plus tard.

Pour revenir sur le Schema Definer, vous pouvez prendre exemple sur le site de schema.org qui est le premier objectif. Vous pouvez prendre l’entité Book (livre) comme exemple.

Pour résumer, on peut dire que le Schema Definer est une administration de la structure de données du site Folkopedia.

C’est pour ça que j’ai parlé d’Angular, il nous faut une interface pour manipuler ce schéma. Peut-être qu’un jour on exposera ce site, comme explication de la démarche scientifique, on verra, rien n’est figé actuellement. La démarche m’importe beaucoup.

Bref, là on a un Lumen installé sur un Vagrant/Homestead, tout frais, et cet articles un point de départ publique. C’est parti !

Case départ, et plus si affinités

Nous y sommes, nous sommes revenu à la case départ, au même niveau qu’au moment du constat sur les performances avec les lumières. Ça compile, ça fonctionne, soupir de soulagement, joie intérieur et stress futur quand on voit ce qu’il a fallu pour en arriver là, et donc qu’il faudra arranger. Mais profitons de l’instant présent, du succès immédiat, de ce haut-fait personnel, de cette joie temporaire mais précieuse.

Profitons en pour rire un peu avec quelques captures et commentaires utiles.

Au commencement : ça fonctionne, d’apparence, bon positionnement, chaque chose à sa place, le curseur bouge, bref idéal. À savoir que les objets complexe comme le coffre ou la porte sont arrivés plus tard car il a fallu traduire vers la 3D objet par objet, et au final se rendre compte que les abstractions ne nous oblige qu’à changer la classe de laquelle on hérite, ce qui est parfait mais pose un problème de logique de conception, en sachant que le TypeScript ne permet pas les Traits, mais veut faire du Mixin auquel je m’y refuse (pas beau, pas maintenable, …).

Comme nous n’avons pas de Traits, j’ai donc été obligé de garder l’héritage, ainsi Element (en tête), devient un SpriteElement, qui à son tour sera décliné en Sprite2DElement et Sprite3DElement et chacun d’eux aura sa version Iso (Iso2DElement et Iso3DElement). La difficulté première est justement ce dupliqua de code obligé par la technique limitée du langage.

Nous avons donc un rendu, complet, chaque objets dessinés et à sa place.

Oh ? Mais que ce passe-t-il ? Ben ça c’est quand on clique sur la porte pour la fermer et que … ben ça plante. Ce qui m’a fait découvrir l’erreur total sur le principe de création des textures (sprites transformé pour WebGL) pour le rendu. En gros, la boucle JE confondais la liste des sprites et les calques de ces sprites, ce qui, vous l’aurez compris, ne fonctionne pas.

La solution n’était pas pour autant aussi trivial que cette définition de l’erreur. L’objet Texture a été transformé en héritage de Sprite, le code de création a été inclus dans cette classe, et l’usage se fait comme Iso2DElement, ce qui est assez comique, mais juste. Faire, défaire, déplacer et recommencer…

Là c’est du grand n’importe quoi ! Non seulement le Héro Squellettore ne tourne plus MAIS en plus son animation fonctionne ! Il ne s’agit donc pas d’un soucis de cache (il n’y a pas de cache en plus). Mais en plus de cela quand on clique pour interagir avec le coffre celui-ci se rend à un arbre, plus ou moins un en bas gauche (variable en plus).

Premier cas : la rotation du héro. Celui-ci est résolu par le cas précédent, la gestion des calques des Sprites, le fait d’inclure la Texture à se niveau, d’y avoir accès et de pouvoir l’appeler au moment du rendu en précisant la rotation manquante jusqu’à lors.

Le second est plus ardu : le clic droit d’interaction fait quelque chose d’incohérent et variable, et c’est surtout cette dernière qui est bizarre. Un code ne change pas tout seul, donc on a quelque chose qui ne va pas et reste silencieux. Et c’est le cas, un try-catch attrapait une erreur dans la fonction de détection de contact avec l’objet (le raycasting), il tombait en erreur sur le cache qui n’existe pas en 3D, donc dur d’évaluer si on touche ou non l’objet si notre référentiel n’existe même pas…

Là, on attaque un chantier, devoir utiliser le cache 2D pour les éléments 3D (sous-entendu les Sprites 2D dans un contexte 3D). Là encore, du fait du langage, obligé de dupliquer un bout de code pour créer le cache à dimension et dessiner, en 2D comme avant, notre objet au complet. On peut se permettre de simplifier car ni la transparence ou la luminosité ne sont utiles pour savoir si on est dessus ou non, autant optimiser. Évidemment on ne fera cette opération que si on veut tester le contact avec notre objet, ce qui ne devrait pas poser de soucis de performances, et de toutes façons on a pas trop le choix sur la technique.

L’optimisation sur la transparence a directement été mise à la racine de la méthode isTouch (détection du contact avec l’objet) pour éviter tant que possible les tests inutiles.

De plus ailleurs, au moment du rendu, on testait la transparence ET la luminosité, mais en rien la luminosité n’affecte le fait d’être affiché ou nous, du coup, j’ai changé en ne gardant que la transparence.

Le contexte du cache et l’initialisation de ce dernier ont été factorisés pour éviter le plus intelligemment possible la répétition de codes et ainsi faciliter la maintenance tout en restant flexible sur la partie variable.

Enfin, ceci ne concerne que le POC, ce que vous voyez actuellement dans les différentes captures, le coffre, qui est une source de lumière pour le moment, était inclus de base. Mais quand je fais mes rendus, pour vous présenter les résultats, je réduis ma fenêtre et donc la taille du subsetMap, et pour une fois, j’ai vu qu’il fonctionnait, en faisant aller mon Héro tout en bas, le coffre est sorti de l’écran, et… pouf ! Freeze complet, je me suis fait engueuler par le navigateur qui me demandait comment utiliser quelque chose de non disponible… :p Oups désolé, on va corriger ça et ajouter une condition ^^. Évidemment on est en POC et tout ce qui concerne les lumières est en chantier, mais ça n’empêche pas de penser à ça. Maintenant c’est fait.

Je peux vous présenter enfin la comparaison avant/après.

À gauche la version précédente en 2D, qui continue de fonctionner en tous points, et à droite la version actuelle en 2D via 3D qui a exactement le même comportement, les performances en plus.

Vous remarquerez qu’en 2D (à gauche) je n’ai pas mis le coffre en source de lumière. C’est la seule différence entre les 2 images. Les arbres sont aléatoires, ça vous l’aviez deviné si vous suivez les articles, donc différence aussi en bas au centre.

Nous voilà donc tel que nous étions il y a plus d’un mois durant les recherches de performances sur les lumières. Nous voilà donc revenu dans une situation « stable » et ainsi pouvoir explorer les 14 millions de suites probables.

Ma première idée, car il manque peu de choses, serait l’impacte couleur de la lumière. Par exemple le coffre émettrait du bleu. Il faudra faire la fusion des couleurs, gérer les impactes individuels et on profiterait pour revoir et finir la propagation des lumières.

Ensuite, il y a bien sur l’éditeur, il faut l’adapter à la 3D et avancer sur l’insertion d’objets sur la carte.

Et puis, enfin, on verra bien l’inspiration du moment. Le but reste le même : le projet HeroQuest; tout en développant le moteur TARS.

Fade to grey(worm)

Outre une petite boutade au Visage de chèvre (GOaT), il sera question ici d’un premier résultat, un retour au gris !

Mais pourquoi donc ? Du gris ? Mais où ça ? Ci-dessous :

Ceci ne vous dit peut-être plus grand chose mais il s’agit du ‘loader’, l’écran de chargement en attendant que la scène courante à venir soit prête. Et oui, il est sur fond gris :).

En fait, l’emphase sur le fond gris représente la finition d’un long processus qui vient de nous occuper ces dernières semaines, derniers mois.

Petit retour en arrière avec les performances au niveau de l’éclairage qui nous a déjà bien embêté et limité notre créativité. J’avais pas envie, mais au final c’est une vraie bonne solution : passer au WebGL.

Évidemment cela ne se fait pas tout seul, ni facilement. Si vous avez lu l’article précédent, vous savez déjà un peu, mais pour résumer : merci au site webglfundamentals.org qui m’a permis de démarrer. On attaque pas ça n’importe comment et le code actuel que je vais vous présenter reste un gros POC non-affiné.

Pour commencer il faut se rendre compte que le contexte WebGL ne travaille pas comme le contexte 2D, du coup, au niveau des classes de TARS et du POC il faut penser une division quelque part, en usage ou en héritage, mais quelque part. Là on se casse déjà les dents.

Le premier gros chantier a été de renommer pour identifier la 2D et séparer là où c’était nécessaire, avec, en bonus, le fait que ça continue de fonctionner…

Ensuite on sort l’huile de coude et on ajoute et duplique les composants pour gérer la partie 3D. Il a fallu donner du mou au Renderer pour lui envoyer des options spécifiques (premultipliedAlpha, alpha) et créer un service (pseudo-temporaire-je-POC-laissez-moi-tranquille) de gestion du contexte 3D avec ses shaders, car ici on y passe directement. Du coup, on ne peut pas démarrer sans que ça soit chargé et prêt, donc un événement à insérer dans le workflow.

De plus, pendant les tests, il a fallu désactiver les scènes ‘world’ et ‘loader’ qui sont IsoScene (isométrique) alors qu’on a pas encore affiché une seule image plate (scène ‘title’). Donc on va peut-être commencer par là.

Après, ça part dans tous les sens, le but est de produire une fonction drawImage tel que celle du contexte 2D, mais à ma sauce permettant son faux polymorphisme ainsi que l’injection d’additifs à destination des shaders tel que la gestion de la transparence, de la luminosité, la modification de couleur, etc. Et quand on a écrit la première version, et corrigée, on a le titre daaboo tel que nous l’avions avant ! Yeah !

Mais bon, pour les IsoElement ce n’est pas aussi simple. En 2D ils ont besoin du contexte 2D, il y a un cache et certaines limitations. En 3D, il n’y a pas de cache, on dessine en temps réel et on a des options via les shaders que la 2D n’a pas. Du coup, on y revient, la découpe 2D/3D et du code spécifique pour chaque.

Au final, tout se passe à l’appel de la fonction drawImage de mon service Sprite3D tout en tenant compte du positionnement XY que 2D fait en 2 temps et en un seul en 3D. Une fois l’appel vers drawImage fait, en gros on a fini et le reste se fait tout seul grâce à l’abstraction mise en place.

Ça dessine mais sans utiliser les attributs de la fonction toujours en développement
On a notre assemblage et correctement positionné !

S’en suit la fameuse finition du fond gris où encore une fois les techniques 2D/3D sont différentes, et donc le ‘clear’ de l’écran avant de faire le rendu. Petit jeu d’héritage et de conditions et hop, on passe la couleur ou une valeur par défaut, en restant générique, et on obtient le résultat tel que montré au début sur fond gris !

La suite se fait impatiente ! Réactiver la scène ‘world’, l’interaction et enfin : la lumière.

Bonus : j’ai eu une idée pour faire varier la lumière en tenant compte du delta du déplacement du personnage et ainsi rendre plus doux le changement de lumière sur chaque Tile. Ceci dit il faudra voir la lourdeur du processus, mais on peut déjà imaginer que nous n’avons besoin de refaire le pathfinding qu’au passage de Tile, et durant le delta l’usage suffit (à préserver donc), mais ça demande un reset de chaque Element quand même, enfin, en WebGL cela n’a plus du tout le même sens, ce qui nous arrange justement !

Angular library – how to – snippet

Suite à mon article précédent j’ai souhaité investiguer, attiré par la curiosité maladive et la difficulté jusqu’à lors de compréhension de cette mécanique. Je vous liste ci dessous quelques articles qui m’ont guidé tant pour démarrer que pour me mélanger les pinceaux.

Pour résumer, si comme moi, vous êtes perdu voici un snippet de démarrage et une copie d’un conseil donné dans le dernier lien.

ng new tars --directory . --create-application=false
ng generate library tars-lib --prefix=tars
ng generate application tars-test

La dernière ligne est utile plus tard quand il faudra tester via une app angular la librairie que l’on souhaite publier.

À noter que mon but n’est pas de passer par NPM pour la publication ni d’aller sur GitHub pour distribuer mon code.

Le conseil du dernier article proposait les lignes ci-dessous dans votre package.json (le premier en root) dans l’espace des scripts.

    "build_lib": "ng build tars-lib",
    "npm_pack": "cd dist/tars-lib && npm pack",
    "package": "npm run build_lib && npm run npm_pack",

Ma prochaine étape est le déplacement du code TARS au sein de l’espace tars-lib créé et de refaire les liens vers le fichier public_api.ts. Ensuite de créer, comme actuellement, une app démo/test, sans inclure l’éditeur, juste une scène et un rendu fonctionnel, simple et expressif.

Une fois validé, je pense que l’on peut alors faire le build de la version de la librairie TARS et créé un nouveau projet dédié pour HeroQuest et là, voir si l’éditeur et la base du jeu prenne bien la lib’.

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.