Intergalacticulaire

C’est l’histoire d’un clic, qui n’avait rien de particulier, mais était destiné à de grandes choses. Il ne le savait pas encore, mais il allait bientôt vivre un grand moment et constater les conséquences de ses actes.

C’est l’histoire d’un clic, droit comme un pique, sur une porte, droite elle aussi, qui pouvait en un même instant faire bouger un caddie, un peu bancale lui, et demander une interaction qui elle allait dans tous les sens.

Cette histoire chers amis, c’est une conclusion, en terme de preuve de concept (POC), qui dure maintenant depuis plusieurs semaines. On y est enfin, la boucle est bouclée et voici l’explication finale.

Pour vous donner cette explication je vais procéder de manière chronologique en séquences. Ceci dit, je ne vais pas me priver de quelques détails. À cela j’ajoute que j’ai opté pour la solution HTML du menu et que ça nous a réservé quelques surprises.

Tout commence par un clic droit, notre isoPlayer (de type isoScene) reçoit l’événement, on détecte le bouton droit et on tente de déterminer sur quoi on a cliqué (le fameux Raycasting). On trouve un Element (ou rien et ça s’arrête là), on lui demande ses coordonnées (oulala…), on dit au Hero que c’est en fonction de cet Element qu’il va s’orienter, puis on attaque la partie déplacement en fonction des coordonnées reçues. On termine par préparer une surveillance de quand il arrivera à destination.

Excepté si le Hero est déjà sur place, nous patientons un instant et on nous rappelleras quand il y sera. La fonction de surveillance sera appelée et dedans, tout simplement, on informe les intéressés, qu’un événement de menu d’interactions a été déclenché, en lui passant les informations recueillies de notre Element cible : coordonnées du bloc à l’écran et la liste des interactions disponibles.

Nous venons de parler des intéressés, c’est à dire ceux qui se sont abonnés à l’événement du menu d’interactions. Petite modification dans notre composant Player (celui qui englobe le rendu et le point de départ de Game), nous avons créé et ajouté un composant InteractionsMenu, dont le but est d’afficher une liste de verbes et de prévenir quand on a cliqué dessus. Notre composant Player s’est abonné et va donc recevoir l’événement dont on a parlé fin du paragraphe précédent.

Du coup que se passe-t-il ensuite ? IsoPlayer est prévenu et peut à son tour préparer une variable à destination du composant InteractionsMenu, qui à son tour va pouvoir s’afficher selon les coordonnées écrans avec la liste données. Jusque là, tout est comme annoncé.

Petit commentaire sur la liste des verbes, bien sur il y a 2 verbes pour une porte, « open » et « close » (actuellement dans notre POC), mais selon l’état actuel de la porte le menu n’affiche que ce qui est disponible.

Vient ensuite le choix du menu, ici il n’y en a plus qu’un vu le filtre cité juste avant. Le composant InteractionsMenu a une sortie sous forme d’événement à son tour. Vous cliquez, il informe ses abonnés, dans le cas présent : le composant Player, qui à son tour, quand déclenché, masquera le menu et demandera à IsoPlayer de gérer l’action.

Pour ce POC, et vu que nous n’avons qu’une porte, la gestion se fait au cas par cas et selon l’état. Donc, de quel type est l’Element ciblé, et en fonction du verbe reçus on contacte la bonne méthode pour lui demander de changer d’état. En interne, c’est à lui de se gérer et mettre sa liste d’interactions à jour.

Nous voilà donc avec un boucle bouclée, un clic est à l’origine du ciblage et nous affiche un menu d’interactions disponibles, on choisi et c’est reparti, on remonte la chaîne pour agir sur l’Element.

Ceci est bien sûr un premier jet, fonctionnel certes, du côté implémentation de TARS (usage de la librairie), mais on peut se poser quelques questions et y voir déjà des choses à régler ou à faire évoluer. Enfin, ceci sera surtout vu au cas par cas tout au long de notre chemin pour nous rapprocher du projet HeroQuest.

Mais du coup, maintenant que ce grand challenge a été relevé, c’est quoi le suivant ? Bonne question, mais je pense que l’éditeur reste la suite logique. C’est à dire, l’édition de la carte, une gestion différente de la souris et de la caméra, un menu interactif pour la gestion du catalogue et interagir avec un Element sélectionné. Il me reste à découper ça en phase et à trouver par où attaquer ^^.

Ours à poil, sens du détail

Hier encore la détection se faisait à l’aide de « bounds », de zones définies par rapport au Sprite pour définir ce qui est susceptible d’être le contenu de ce qui ne l’est pas. Nous avions un arbre, un sol, des murs, jusque là c’était évident, 5 à 6 points suffisent pour les définir grosso-modo. Mais, depuis nous nous sommes mis en tête de mettre une porte, qui plus est ouverte, et là, c’est un besoin de 5 zones et ce par direction, soit 20 repérages pour un seul objet et encore, sans animation.

Vous l’aurez compris, le but n’est pas de se compliquer la vie, alors développons une solution : la détection par transparence de l’objet.

Nos images sont des PNG, donc un format préservant la transparence, une fois dans un « canvas » (l’objet HTML), nous pouvons demander à celui-ci de nous donner la valeur d’un pixel. Il nous suffit, dans notre méthode de détection existante, au lieu d’utiliser les « bounds », mais en gardant tous les calculs préalables, d’utiliser notre « canvas » afin de tester la valeur du pixel sous notre souris.

Je schématise bien sur, cela demande un environnement vierge, une remise à zéro des valeurs pour comparaison correcte, le dessin de l’image, la capture du point, son analyse, enfin bref, 4-5 lignes et c’est joué.

Pour vous ça revient à tester si la souris touche du rose (qui sera la transparence) ou l’objet, ici notre porte. Ce qui nous rappelle les belles années des jeux « point & click », comme Monkey Island, Loom, Sam & Max etc, basé sur ce genre de sprite en fond rose ou vert une fois décortiqué.

Ceci dit, ça n’a pas été aussi simple, il a fallu modifier les interfaces de configuration des animations, détecter si on veut travailler en Alpha ou en Bounds, rendre ça « sexy » et pratique.

Ce qui nous a également amené à découvrir des erreurs planquées, car ce sont des cas qui n’ont pas encore été testés. Il s’agit de notre porte, ben tiens, encore elle. Elle est définie par 2 Sprites et n’en affiche qu’un à la fois selon son état. MAIS le calcul de taille est basé sur la somme des 2 et la détection les parcoure également. Imaginez une seconde le bordel quand vous ne voyez la porte que normalement, alors que le système manipule tout à fait autre chose.

Après de longues recherches, j’ai introduit la notion de Structure, permettant à un Element défini par X Sprites (un tronc et un feuillage), de les manipuler sur conditions (3 blocs de tronc puis le feuillage) et ainsi de travailler sur une base connue et manipulable (par une classe d’extension). Ainsi notre porte répond à la question « Quelle est ta structure ? » et les méthodes en ayant besoin ne sont plus en erreur, et correspondent à vos attentes.

Maintenant que nous avons la détection sur la porte, et les « verbes », que j’ai ajouté dans la foulée. Nous allons pouvoir « demander » à l’objet trouvé, non plus sa nature comme vu précédemment, mais les interactions possibles ! À nous de les afficher comme bon nous semble et de définir comment nous allons gérer ces interactions.

Mouse to be alive

Tel un tube disco et des déhanchés sur le « dance floor », c’est l’euphorie du clic à présent ! La séparation clic gauche pour le déplacement et le droit pour l’interaction (avec déplacement si besoin et possible) est fait, et ce n’était pas une sinécure !

Dis comme ça, vu qu’on avait déjà les 2 fonctionnalités, rien de neuf sous le soleil mais cela a soulevé des questions, qui ont soulevées la mousse des rochers du début de projet et créé un vrai bordel et remises en question.

Nous en revenons au Raycasting (détecter un objet quand on clique dessus), car désormais nous avons un GridBlock à gérer et ses 7 zones. Nous ne parlerons pas du besoin de séparer le côté spécifique des 7 zones avec un GridBlock générique et dont les IsoScene devront prendre la gestion à l’usage, c’est connu et ça attendra une prochaine version une fois que la poussière du champs de bataille sera retombée.

Le cycle est le suivant :

  • Cliquez (quel bouton ?) droit sur un arbre ou un mur.
  • L’IsoPlayer prend l’événement, détecte le clic droit et demande quel est l’élément « Raycasté » (on dira ça ainsi, ou touché par le lancé du rayon sacré venant de la sainte souris…).
  • Celui-ci demande alors au service Iso de s’en occuper et plus vite que ça !
  • Le service parcours le subsetMap, et demande à chaque GridBlock de faire de même pour les Elements.
  • Du coup le GridBlock demande à ses zones de consulter chaque Element et de voir s’il y a contact.
  • Oui / Non : l’histoire nous le dira !

Première remarque, la manière dont on parcours le subsetMap, on partait de la coordonnées du joueurs et on recalculait ses dimensions pour faire une boucle, je simplifie, mais pas bien du tout ! Après 2 efforts, et surtout l’idée que la grille n’est pas carrée mais un losange, on retiendra que pour chaque colonne et ligne de cette grille on cherche le minimum et maximum afin de la parcourir efficacement et sans artifice inutile.

Notez également qu’un tableau ne peut avoir d’index négatif, ceux-ci seront considérés comme des propriétés (‘-‘ étant un caractère au final) et du coup un forEach, length, etc. ne sont d’aucune aide, privilégiez le « for … in » alors. Ceci dit, nous avons décidé de ne pas les gérer, ce qui permettra une forme d’optimisation.

Ensuite, nous avons le niveau de GridBlock qui doit orchestrer le parcours des zones dans le sens inverse de l’ordre de dessins pour tester le Raycasting. Encore une fois, il faut faire attention à ne pas se répéter inutilement et surtout mettre le bon code au bon endroit, GridBlock ne doit que gérer ses zones. Ainsi, après un petit « refactoring » (refonte), on a une fonction de calcul de hauteur d’une zone (car le Raycasting travaille à l’envers du dessin), une fonction de détection par zone (en demandant à chaque Element si ça touche :p) et la fonction d’orchestration principale pour gérer l’ordre des zones et l’ensemble de l’opération. C’est ce qui m’a pris du temps à rendre joli.

Un code laid n’est pas un bon code

À cet instant, chaque zone est bien ordonnée, testée et aucun code n’est dupliqué. C’est élégant, fonctionnel (surtout) et avec cette refonte, un horizon de généricité se dessine.

La méthode a été reportée sur la fonction de dessin qui souffrait des mêmes manques. On peut donc imaginer avec plus de facilité sa prochaine version, son évolution, comme dit en début d’article.

Après une, deux, semaines, nous revoilà au début de notre schéma pour obtenir l’interaction, point 1) le clic sur l’objet concerné.

Notre but reste un clic sur la porte, faire apparaître un menu quelconque et modifier son état ouvert/fermé. À partir de là on ouvre beaucoup de possibilités à explorer et en découvrir les limites, à repousser toujours plus loin.

En parlant de menu, une question se pose, profiter du HTML à notre disposition ou définir une interface dessinée. La première existe déjà et aura ses avantages / inconvénients, la seconde est a créer de toutes pièces et permet donc une maîtrise complète (événements, animation, éléments inexistant en HTML, etc.) mais demandera beaucoup plus de temps à concevoir.

Enfin, avant de penser à ça, nous allons devoir définir ce qu’est une interaction, lesquelles sont possibles, comment les récupérer et les définir. Un beau challenge encore. 🙂

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.