Je suis Traversable

Je suis … Traversable !!! Comme un fantôme ! Ou du moins comme une porte :). Exactement, une porte, ce sujet qui nous tracasse depuis qu’on a des murs.

Mais qu’est-ce qu’une porte ? C’est un Element, qui se situe au niveau d’une zone de mur et à qui le pathfinder doit poser la question si oui ou non on peut passer ce « mur ». Il doit donc demander s’il est Traversable.

Côté technique il y a 2 aspects : étendre Element au travers d’une classe Door et définir une interface Traversable ainsi qu’une astuce typescript, une fonction dédiée instanceOfTraversable (car nativement on ne peut tester si une classe étend ou non une interface).

Ne pas oublier que pour ce faire nous avons également ajouté les images, les définitions dans les fichiers de ressources et modifié le générateur de map pour notre POC comme précédemment, petit rappel.

Pour les codeurs qui chercheraient de l’aide sur le sujet des interfaces voici comment s’y prendre :

export interface Traversable {
  isTraversable(): boolean;
}

export function instanceOfTraversable(object: any): object is Traversable {
  return 'isTraversable' in object;
}

Ainsi notre DoorElement basé sur Element nous permet d’utiliser cette interface et de répondre à la question. Notez que la question n’est pas posée par le pathfinder lui-même mais par la fonction de validation qu’on lui donne. Ceci reste donc côté implémentation comme désiré.

Comme vous pouvez le remarquer on a bien notre porte… enfin 2 !

Mais qu’est-ce qui a bien pu se passer ? Ben en fait c’est tout con, première règle élémentaire de la Robotique de notre moteur TARS, tout est empilé ! Et quand Element reçoit des spritesRefs multiples, il stack naturellement, jusqu’à ce qu’on surcharge sa classe et sa méthode draw() pour changer tout ça :). Et c’était prévu, mais, car il y a un mais, il y avait moyen d’améliorer l’accès à la fonction de dessin individuel.

Dans notre cas nous avons 2 sprites chargé pour un DoorElement, ouvert et fermé (ah ben bravo !). Nous ajoutons un statut à la porte, on en profite pour faire une interface surchargeant SceneElement pour les config et un « enum » pour structurer Open et Closed comme valeur possible du statut de la porte, le tout rendant l’objet porte bien structuré quand on veut en ajouter une.

Enfin, suivant le statut, un seul Sprite sera affiché et grâce à la fonction drawOne() qu’on a ajouté, le tout est rendu plus simple.

Actuellement il n’y a pas encore d’interaction, donc la vidéo ci-dessus est illustrée par 2 compilations distinctes.

Ce qui nous amène à une observation, l’eau ça mouille… et les arbres cachent la case derrière eux (en NE), du coup le raycasting actuellement utilisé au déplacement de la souris, ne permet pas aisément de sélectionner la case visée, il faut aller sur le bord de l’arbre sur 1-2 pixels.

Après réflexion, vu qu’on tend à implémenter les interactions, à revoir cette gestion de la souris. Ceci vous étonnera surement, mais elle a plusieurs boutons, la souris. On pourrait donc en utiliser un pour se déplacer, sans raycasting, juste un survol des cases, et un autre pour tenter d’interagir, si la cible le permet.

Il y aurait également la réflexion sur le curseur qui serait caché, car son rendu se fait au moment de sa case, et si un mur ou un arbre le masque, vous ne savez pas vraiment où il est si vous n’avez pas suivi. On pourrait imaginer un postDraw (un rendu d’après), permettant alors de se redessiner après tout, sous une forme allégée, comme un contour en transparence ou surbrillance. Il faudra également savoir qui a besoin de ce postDraw, pour éviter de parcourir à nouveau l’ensemble du subsetMap. Soit par préenregistrement, au moment de l’init du subset par exemple vu qu’on parcours la map, ou au moment du rendu vu qu’on passe dessus également. C’est peut-être cette dernière piste que je regarderais.

Les mexicains vont pas aimer

Des murs !!! Ça y est, le système affiche proprement les murs définit, actuellement définit en code pour les besoins du POC (test). Vous voyez ici, dans l’ordre haut gauche vers bas droite, un N et un E, puis un S, ensuite un W et E, et enfin encore un W. Ainsi l’on constate que tout se met bien même côte à côte (EW).

Ça c’était la partie facile, au final, vu que tout était prêt avec GridBlock. La difficulté à présent est de modifier le pathfinder pour qu’il tienne compte des murs.

Premier constat, initialement nous tenons compte de l’élévation entre blocs de manière claire au sein du code, ce qui va à l’encontre d’une idée générique (entre l’idée de la librairie pathfinder et son usage en fonction du projet). Ceci dit c’était essentiel pour les POC précédents. C’est donc notre point d’entrée, il faut un moyen d’accepter ou non le fait de passer d’une case à une autre.

On retire donc tout ce qui touche à l’élévation et hauteur enregistrée jusque là, on crée une fonction que l’on envoie au pathfinder pour sa résolution, ainsi il ne connait rien de votre projet et cela vous permet de définir les règles d’acceptation, on le nommera le Validator. Pour un premier essai que tout continue de fonctionner comme avant, il renvoie toujours oui, et ça fonctionne, cette mécanique est prometteuse.

Ensuite vient le fait d’avoir connaissance des murs, car jusque là, tout ce que le préparateur de la grille du pathfinder sait c’est une coordonnée (x, y, vu qu’on a retiré l’élévation). Mais dans notre Validator nous n’avons aucun accès à la Map ou au subsetMap, nous avons juste la structure de coordonnées du pathfinder (précision importante ici).

Il faudra donc ajouter le GridBlock consulté lors de la préparation de la grille à la structure de coordonnée du pathfinder, pour garder un accès et le consulter plus tard au moment de la résolution du chemin et donc de la validation (passage d’une case à une autre) et modifier ci – et – là le fait d’utiliser cette structure au lieu de simple coordonées [x, y].

Ceci étant fait, nous avons donc un pathfinder, qui a une grille avec accès à la Map par référence, et une méthode de validation de passage le rendant générique. Il ne nous reste plus qu’à écrire ces fameuses règles autorisant ou non le passage d’une case à une autre, notre soucis : les murs.

Nouvelle question comment savoir quel murs comparer sur une case et sur l’autre ? Car oui, comme dans l’exemple initial en début d’article, on a deux murs collés EW, chaque sur leur case, il faut tenir compte des deux. Et ce n’est pas tout, comment savoir que c’est le N et le S qu’il faut comparer alors que l’on reçoit juste 2 coordonnées en entrée du Validator.

Nouveau défit, nouvelles idées. Nous avons au sein d’Element une fonction permettant de définir l’orientation. C’est grâce à cela que le caddie se tourne suivant son chemin. En divisant cette fonction en deux on a le getDirection() qui nous manquait. Dès lors la théorie à appliquer pour ce premier POC est assez simple, suivant l’orientation NESW, on compare les 2 murs des 2 cases, par exemple je vais vers le N, je compare mon mur N d’origine et le S de destination, et ainsi de suite selon toute logique :).

Et ça fonctionne !

Maintenant des portes et des interactions, car un mur avec une pancarte par exemple, sera lisible (interaction) mais : de quel côté ? condition(s) de mur mitoyen ? Etc. Beaucoup de questions qui vont demander de modifier les fonctions de l’IsoPlayer et du PlayerElement car, au final, ce ne sont que des détections ^^…

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.

Bounty Train – petit guide de survie

Bounty Train est un jeu développé par Corbie Games depuis 2015, édité par Daedalic Entertainment et sorti en 2017 vous ramenant au Far West au commande d’un train. Votre père est mort dans des circonstances douteuses et vous héritez des parts de son empire de transport ferroviaire en difficulté.

Vous voilà donc au commande d’un petit train et d’un wagon de marchandises où le tutoriel vous expliquera les rudiments du jeux. Le tout dans une ambiance de guerre de sécession et de cow-boys et indiens. Ceci étant fait, vous êtes absolument prêt à vous faire défoncer par le jeu.

Le jeu a en effet une difficulté très élevée, faisant passer n’importe quel boss pour un simple péon. Le premier conseil, sauf si vous aimez directement attaquer avec zéro connaissance de la mécanique de jeu et un défis énorme, sera de diminuer cette difficulté. Là c’est sympa c’est personnalisable. Sans honte ou difficulté d’ego prenez le mode facile ou bébé, y a pas de mal, vous aurez quand même de quoi bien rater votre partie.

Outre une difficulté des ennemis, des événements, des délais, ou des affaires, c’est votre sens de l’organisation qui fera la différence, ainsi qu’un peu de mémoire… c’est pas gagné. Notez également le côté aléatoire de certains événements et endroit où trouver certaines personnes, chaque part sera différente, ça c’est sympa.

Bien démarrer

Vous commencerez à Portland, la ville le plus au nord des États Unis et le tutoriel que je vous conseille de bien lire vous emmènera à Boston faire des affaires. Vous devrez marchander d’une ville à l’autre pour avoir votre premier pécule, c’est là que vous verrez la différence de difficulté entre facile et normal, grappiller des petits dollar ou profiter du jeux sans perdre une heure pour avoir quelques billets.

L’aventure peut commencer quand vous avez carte blanche post ‘tuto’ et là il vaut mieux un petit récapitulatif.

  • Faites gaffe à vos finances ! par d’argent > pas de charbon > pas de déplacement > « game over », et vendre vos wagons ne sera pas une vraie solution.
  • Enregistrez-vous au bureau de poste de chaque ville visitée.
  • À chaque déplacement, regardez si vous ne pouvez pas embarquer une personne, une tâche de la mairie ou des biens d’une ville à l’autre (utilisez le comparateur).
  • Attention également par où vous passez. Des bandits, des contrôles, ou des événements qui apparaissent en plein déplacement peuvent vous emmener au « game over ». Une petite sauvegarde ne fait jamais de mal, et c’est toujours dispo avant de se mettre en route. Segmentez vos déplacements en cas de doutes et n’hésitez pas à changer de chemin par rapport au « GPS ».
  • La réputation, vous la gagnez en transportant des voyageurs et en faisant des missions de mairie. Attention aux commérages et aux échec de mission qui peuvent fortement vous nuire.
  • Attention au point 1, vos finances !

Les enchères

Une mécanique du jeu, qui s’active sur la fin du début de l’histoire, la chasse aux parts de l’entreprise, vous demande souvent de faire des enchères pour les obtenir. Ceci dit vous ne savez pas à quoi vous vous attendez, ni combien prévoir, tout en oubliant pas de garder assez pour vos prochains déplacement et en faisant attentions aux différents délais de missions en cours. Juste bien faire gaffe.

De mon expérience de jeu, je me garde un 10.000$-12.000$ si l’enchère démarre à 1000. Rien ne sert d’enchérir au début, attendez que les gens quittent et que le dernier donne son prix final, un cercle rouge rempli le prix, avant la fin mettez le plus petit montant pour avoir la main. Généralement il ne surenchérit pas, ou une fois (de mon expérience de jeu).

Attention d’avoir assez de réputation pour pouvoir accéder aux enchères, vous n’y penserez jamais tôt assez et vous raterez des occasions.

La banque

Une possibilité du jeu vous offre le moyen de faire fructifier votre argent. Certaines villes ont une banque et vous pouvez déposer un montant pour obtenir une plus-value sur une période donnée. Si vous voulez un succès, obtenez 1.000$ d’intérêt. À ne faire que si vous avez 10.000$-15.000$ et que vous pouvez y laisser au moins 5.000$ sinon autant allez faire des missions de mairie.

Faire de l’argent

Le soucis, en dehors des délais et de la masse transportable de votre train, sera l’argent. La mairie sera ce qui vous rapportera le plus. Attention à votre réputation également ici qui vous donnera accès a des missions plus juteuse.

Également, certains marchandages d’une ville à l’autre peuvent vous aider rapidement à faire de l’argent. Par exemple New York produit beaucoup de matières fort demandées ailleurs à des prix intéressant. Attention dans le comparateur, de ne pas oublier de regarder prix d’achat ET de vente, ou à droite, au quai de chargement de voir les prix achats/ventes. Pensez « bijoux » entre New York et Philadelphie ;).

Le train

Le truc que l’on veut améliorer mais qui va vous prendre la tête en fonction des missions et des délais, c’est bien votre train. De plus chaque ville ne propose pas la même chose, pensez à prendre des notes et à faire attention aux annonces qui arriveront avec le temps, comme par exemple un nouveau wagon, une nouvelle loco, etc.

Fonction de vos finances, n’hésitez pas à prendre la loco suivante en faisant attention à la masse tractable, le poids de vos wagons actuels ou à venir ainsi qu’au charbon, car une partie des loco ont peu de réserve et non évoluable.

À ce propos, pensez à améliorer d’abord votre train au niveau puissance et quantité de charbon transportable. Votre wagon de marchandise peut être également amélioré concernant la place disponible. Le reste des amélioration dépend de vos finances et envies, les autres bonus ne me semblent pas personnellement utile, vous aurez quand même le feu et des défauts.

D’ailleurs surveillez vos wagons et train à 25%+ de chance d’avaries. Parfois remplacer le wagon sera un bien meilleur investissement que de réparer et de perdre du temps.