Journal Nouvelles du jeu Harmonist : refonte de l'interface grâce à Gruid

Posté par  (site Web personnel) . Licence CC By‑SA.
Étiquettes :
32
24
fév.
2021

Sommaire

Bonjour Nal,

Je t'écris pour te parler d'une nouvelle version du jeu roguelike d'infiltration Harmonist et de Gruid, une bibliothèque en Go que j'ai écrite pour refaire l'interface d'Harmonist. J'en ai parlé ici il y a un peu plus d'un an déjà, Harmonist est un jeu à propos d'un petit singe, nommé Syu, qui doit sauver son amie Shaedra, emprisonnée par le clan Dayoriah des Souterrains !

Harmonist Intro

Harmonist v0.4.0

Cette nouvelle version d'Harmonist apporte une refonte complète de l'interface. Parmi les changements visibles, il y a :

  • Une interface plus interactive : des informations sont affichées à la volée en fonction du contexte ou du mouvement de la souris, à des emplacements adéquats.
  • Pour la version native avec tuiles, Tk a été abandonné au profit de SDL. SDL est plus rapide pour dessiner les tuiles et offre aussi la possibilité de faire du zoom.
  • L'interaction entre l'utilisation du clavier et la souris est plus harmonieuse. Par exemple, utiliser la souris pour examiner ne passe plus le clavier en mode examination.
  • Les animations maintenant s'interrompent si on envoie de nouvelles commandes avec le clavier ou la souris.
  • La fonctionnalité de replay permet maintenant de naviguer plus facilement en avant et en arrière.

Capture d'écran illustrant l'algorithme de vision

Les changements au niveau du gameplay sont peu nombreux, essentiellement des détails et corrections de bugs mineures, à un point important près : un nouvel algorithme de vision est utilisé. Cet algorithme est une combinaison (intersection) de deux algorithmes.

  • L'algorithme précédent, qui offrait une visibilité et passabilité non binaires : par exemple les arbustes réduisent le champ de vision, mais ne le bloquent pas totalement, contrairement aux murs. Par contre, ce premier algorithme permettait de voir un peu trop bien au niveau des coins avec des murs.
  • L'algorithme nommé symmetric shadow casting, qui offre des ombres expansives, tout en restant symétrique (si on voit une case depuis une autre l'inverse est aussi vrai). L'intersection de deux algorithmes symétriques étant symétrique, on obtient un nouvel algorithme qui combine les propriétés souhaitées !

Gruid

Gruid est la bibliothèque Go que j'ai écrite en vue de cette refonte de l'interface dans Harmonist. Je vais te raconter un peu comment ça marche et mon expérience avec.

C'est une bibliothèque qui reprend des idées de sources diverses :

  • Une architecture inspirée de l'architecture du langage fonctionnel Elm, mais adaptée au concept de grille (plutôt que d'HTML) et à Go (moins fonctionnel, plus performant).
  • Utilisation d'une grille, un peu comme les bibliothèques type curses, mais avec trois méthodes de rendu différentes : terminal, SDL2 ou navigateur (WebAssembly). Les méthodes de rendu graphique peuvent ainsi utiliser des tuiles. Dans tous les cas, on utilise la méthode classique qui consiste à redessiner uniquement les cases qui ont changé. Ça permet d'avoir de très bonnes performances, même sans accélération graphique, comme pour le terminal (même avec des tuiles).
  • Une structure de données pour représenter les grilles inspirée des slices (des portions de tableaux) de Go et de la bibliothèque d'images de la bibliothèque standard.

En plus d'un package pour le rendu fondé sur les idées ci-dessus, Gruid offre des fonctionnalités typiquement utilisées pour les jeux à base de grille : des algorithmes de recherche de chemins (dont JPS), des algorithmes de génération de cartes (marches aléatoires et automates cellulaires), manipulation de pièces préfabriquées, des algorithmes de vision et une liste de priorité pour évènements.

Architecture à la Elm

Le langage utilise une boucle principale inspirée de Elm. Une application doit définir :

  • Une fonction qui actualise un modèle (l'état du programme/jeu) en réponse à des messages (le plus souvent des entrées utilisateur, clavier ou souris, mais pas que).
  • Une fonction qui renvoie un rendu de l'état du modèle.

Ça ressemble assez aux typiques Update/Draw qu'on retrouve dans beaucoup de frameworks de jeux, mais il y a des différences importantes :

  • Update est appelée après réception de messages particuliers et non à des intervalles réguliers (le typique 60 FPS).
  • Update renvoie éventuellement une commande : une fonction exécutée de façon concurrente et qui renvoie un message qui sera reçu dans un appel futur d'Update. Une telle commande peut être utilisée par exemple pour un timer (envoyer un message au bout d'un certain temps), écouter sur un socket, ou effectuer des entrées-sorties de façon concurrente en général. Ça offre une façon bien pratique et intégrée de gérer la concurrence dans l'interface utilisateur.
  • Draw (appelé View dans Elm) renvoie un résultat qui est ensuite envoyé au système de rendu. Dans Elm, ça renvoie de l'HTML. Dans Gruid, ça renvoie une grille (éventuellement un sous-rectangle). D'autres possibilités existent. Par exemple une autre framework similaire et inspiré d'Elm (bubbletea), qui lui cible les applications en ligne de commande uniquement, a choisi de renvoyer des chaînes de caractères.

Dans Go, l'interface que doit satisfaire un modèle de programme est la suivante :

type Model interface {
    Update(Msg) Effect
    Draw() Grid
}

Grid est le type pour représenter les grilles et Effect est le type pour représenter les commandes. Enfin, Msg est le type des messages, qui peuvent être de toutes sortes.

Le type Effect

Pourquoi « Effect » ? Parce qu'il y a en fait deux types de fonctions qui renvoient des messages. Elles sont appelées managed effects dans Elm.

Il y a d'une part les commandes déjà mentionnées, exécutées de façon concurrente. Elles s'exécutent puis renvoient un message. Leur type est le suivant :

type Cmd func() Msg

D'autre part, on a les souscriptions : exécutées de façon concurrente également, elles envoient un nombre arbitraire de messages sur un canal.

type Sub func(ctx context.Context, chan<- Msg) Msg

Le contexte fourni sert à notifier la souscription d'arrêter son exécution, par exemple lors de la fermeture. Les souscriptions s'utilisent moins que les commandes, mais sont utiles pour les fonctions qui s'exécutent sur une longue durée et doivent notifier leur progrès, ou pour des fonctions qui s'exécutent sur toute la durée du programme : par exemple, écouter la présence de signaux (comme SIGINT ou SIGTERM), puis notifier avec un message l'Update de l'application pour lui permettre de réagir.

Elm traite les souscriptions de façon un peu différente par rapport aux commandes, mais l'idée reste similaire.

Le type Effect de gruid est représenté par une interface. Cette interface est satisfaite exactement par les deux types Cmd et Sub (la méthode n'est pas exportée). Elle joue le même rôle qu'un type somme pour les commandes et souscriptions.

Le type Grid

J'ai mine de rien mis pas mal de temps à finaliser l'API de ce type !

La représentation utilisée ressemble, à certains détails près, fortement à celle qu'on peut retrouver dans certaines bibliothèques numériques. Essentiellement, c'est un struct représentant un couple (pointeur, range):

  • Le pointeur pointe vers une grille sous-jacente, représentée par un simple tableau compact (slice de Go).
  • La composante range délimite le sous-rectangle avec lequel on travaille (par exemple dans le cas d'un widget).

La possibilité de travailler sur des sous-rectangles est très pratique à l'heure de programmer des widgets. La façon de créer des sous-rectangles, ainsi que le code de certaines fonctions, sont fortement inspirés de la bibliothèque image de la bibliothèque standard qui permet de facilement manipuler des rectangles et des points. Certaines optimisations sont inspirées de la bibliothèque d'images, par exemple pour remplir efficacement une sous-grille en faisant des appels astucieux à la fonction copy de Go (qui est implémentée en assembleur sous le tapis) après avoir rempli une première ligne manuellement.

L'API de la grille en soi est par contre plus proche de celle des slices en Go : on trouvera par exemple une fonction Copy qui copie une grille dans une autre (éventuellement des sous-grilles de la même grille) dont le résultat ne dépend pas de l'intersection éventuelle entre les deux grilles.

L'API a aussi quelques particularités adaptées à sa vocation de dessin : dessiner par inadvertance en dehors de la grille ne fait rien. Parce que oui, un jeu ne doit pas crasher parce qu'un widget aurait été mal placé ou dimensionné !

Par ailleurs, étant plutôt content avec ce type Grid, j'ai finalement adopté une API quasiment identique (avec des trucs en plus) pour la représentation interne des cartes. Les algorithmes de génération de cartes peuvent ainsi être configurés pour modifier uniquement un sous-rectangle de la carte au besoin.

Recherche de chemins

Gruid offre plusieurs algorithmes de recherche de chemins : A*, Dijkstra, JPS (une optimisation d'A* pour les grilles) et calculs de composantes connexes, ainsi qu'un algorithme efficace de simple parcours en largeur.

Harmonist utilise A* et JPS pour calculer des chemins, par exemple pour le joueur ou les monstres. A* est plus lent que JPS (qui est surprenamment rapide), mais plus flexible et permet de privilégier certains chemins par rapport à d'autres et donc de faire de l'IA (par exemple un monstre essaiera de contourner d'autres monstres pour attaquer le joueur). Suivant les cas, l'un ou l'autre est utilisé.

L'algorithme de Dijkstra est utilisé dans Harmonist par exemple pour la propagation du bruit. Le simple parcours en largeur est utilisé pour l'exploration automatique (il s'agit de trouver un chemin vers la zone inconnue la plus proche).

Performances

Un certain effort a été fait dans la bibliothèque pour rendre ses algorithmes efficaces. En particulier, les allocations mémoire sont réduites grâce à des caches sous le tapis quand j'ai estimé que ça valait la peine.

Par exemple, une recherche de chemins sur une carte 80x24 spécialement conçue pour être difficile donne les résultats suivants pour A* et JPS :

BenchmarkJPSPassable1-2                    44414             27329 ns/op
BenchmarkJPSPassable1NoDiags-2             43028             27867 ns/op
BenchmarkAstarPassable1-2                   2940            402059 ns/op
BenchmarkAstarPassable1NoDiags-2            4210            285463 ns/op

Une bonne partie de l'effort de calcul dans l'algorithme A* se trouve au niveau de la gestion d'une file de priorité (le plus souvent implémentée par un tas). Cette file de priorité permet de connaître la case suivante à explorer en priorité. Une mauvaise gestion pourrait conduire à des chemins qui ne seraient pas les plus courts !

Le secret de l'algorithme JPS est de tenir compte de la structure très particulière d'une grille et de faire des « sauts » orthogonaux et diagonaux astucieux et éviter ainsi d'ajouter certaines cases dans cette file de priorité. L'algorithme est particulièrement bien vulgarisé sur ce site.

Dans les cas simples, comme des chemins courts avec peu d'obstacles ou de petits obstacles (assez courant en pratique), les performances des deux algorithmes sont beaucoup plus similaires et dépassent rarement les 20μs.

Dans le benchmark ci-dessus, la carte utilisée était la suivante (pour le cas BenchmarkJPSPassable1 avec diagonales autorisées) :

JPS en action

Les murs sont les #. Il s'agissait de trouver un chemin d'en bas à gauche (O) vers en bas à droite (G), ce qui nécessitait de faire de gros zigzags. Les o correspondent aux points du chemin trouvé et les X correspondent aux points décisifs à partir desquels un saut a été effectué : ce sont les seuls points qui sont ajoutés à une liste de priorité de recherche. En comparaison, A* aurait rempli presque toute la carte avec des X.

Dans Harmonist, l'ensemble des calculs d'un tour (en incluant également les algorithmes de vision), se fait normalement toujours en dessous de 1ms. La génération des cartes et initialisation complète d'un niveau se font en environ 5ms (ça dépend du type de carte).

Tous ces résultats sont sur une machine plutôt vieille qui n'était déjà pas très puissante à l'époque (un Intel Celeron avec deux cœurs d'il y a quelque chose comme sept ans).

Pour les versions natives (terminal et SDL), l'intérêt des optimisations est limité : entre rapide et très rapide l'utilisateur ne verra que rarement la différence sur une machine moderne. Ça permet par contre au programmeur utilisant la bibliothèque de moins s'inquiéter des performances et ne pas être tenté de faire des optimisations prématurées. Par ailleurs, ça a permis de rendre rapide la version navigateur également ! Et puis, je me suis bien amusé :-)

Extensibilité

Gruid permet d'écrire de nouveau drivers pour des moteurs de rendu différents. Par exemple, il serait possible d'écrire un moteur de rendu qui fonctionne en mode serveur, ou un moteur de rendu qui utilise une bibliothèque graphique différente que SDL, ou une bibliothèque terminal autre que tcell. Il suffit d'implémenter l'interface Driver pour cela.

La suite

Pour la suite, j'espère utiliser gruid dans de nouveaux projets de roguelikes. J'ai quelques idées en cours, mais rien de très concret encore. La phase de design du gameplay restera sans doute toujours la plus difficile… mais intéressante !

Autrement, j'espère que ce journal encouragera certains à tenter d'écrire un roguelike ! En tout cas, j'ai perso appris beaucoup de choses en programmant des roguelikes, c'est une expérience assez variée tout en étant réalisable par une seule personne. Et puis, je trouve ça plutôt rigolo :-)

D'ailleurs, ça me fait penser : bientôt il y a le challenge 7DRL, ça peut intéresser ceux qui aiment les petites compétitions. Il s'agit, comme tous les ans, d'écrire un mini roguelike en 7 jours ! Perso, j'ai jamais participé, je trouve les deadlines un peu stressantes, mais je regarde toujours après coup ce qu'ont fait les autres : on y trouve souvent des perles (pas toujours libres, par contre).

  • # Petit commentaire

    Posté par  . Évalué à 5 (+3/-0).

    Salut,

    C'est cool ton projet, et bon courage pour la suite !

    Perso, quand j'ai tenté le voyageur de commerce pour la première fois, j'arrivais à faire 5 maisons, guère plus…

    A* est générique, d'où sa lenteur. Dijkstra optimise un peu, en terme de calcul de longueur de chemin. Mais !

    (oui, il y a un mais :p)

    J'aime bien ces trucs.

    Tu peux regarder aussi du côté des algos à "charge" (genre, tu peux ne transférer qu'une quantité donnée sur une "ligne").

    Bon amusement ;)

  • # Ah l'univers de Shaedra !

    Posté par  (site Web personnel) . Évalué à 4 (+3/-1).

    C'est super sympa d'avoir fait ce petit jeu. Et bravo pour tout.
    Il va falloir que j'essaie.

    Designeuse de masques pour sphéniscidés.

  • # <3

    Posté par  . Évalué à 3 (+2/-1).

    Il est tout mimi ton jeu :)

    BeOS le faisait il y a 20 ans !

  • # Sumpa d'avoir des news !

    Posté par  (site Web personnel) . Évalué à 3 (+3/-1).

    Ça fait plaisir d'avoir des nouvelles un an après, merci !

    Blog: http://blog.bux.fr, github: http://github.com/buxx

  • # retour

    Posté par  . Évalué à 5 (+3/-0).

    vraiment génial ce jeu. J'y avais joué avec plaisir lors de ta première dépêche en 2019, et l'avait un peu oublié je dois avouer (on compile le truc, on prend plaisir à y jouer, et quand on ne l'a plus sous les yeux on passe à autre chose). Je vais le noter dans mes jeux à jouer en priorité, j'aimerais bien le terminer.

    Les petites améliorations depuis la 0.1 sont appréciables, l'histoire et la manière de jouer sont toujours aussi chouette, ça change des trucs où on doit tuer direct les ennemis.

    Ce que j'ai pu remarquer :

    • la doc n'indique toujours pas comment compiler le projet depuis le code source télécharger. Même sur les projets avec un simple configure et make, c'est souvent précisé ("type ./configure && make" par exemple), pour du go qui est moins courant, ça pourrait être utile d'indiquer la procédure exacte, au moins juste les commandes. (y compris comment intégrer le code de gruid)

    • du coup je suis passé sur "go get -u --tags sdl git.tuxfamily.org/harmonist/harmonist.git".

    go: finding git.tuxfamily.org/harmonist/harmonist.git v0.4.0
    go: finding git.tuxfamily.org/harmonist/harmonist.git v0.4.0
    go: downloading git.tuxfamily.org/harmonist/harmonist.git v0.4.0
    verifying git.tuxfamily.org/harmonist/harmonist.git@v0.4.0: git.tuxfamily.org/harmonist/harmonist.git@v0.4.0: reading https://sum.golang.org/lookup/git.tuxfamily.org/harmonist/harmonist.git@v0.4.0: 410 Gone
    

    le 410 gone indique un problème j'imagine. J'ai relancé la même commande, et ça m'a installé la version 0.3 à la place (en mode non graphique)

    go get -u --tags sdl git.tuxfamily.org/harmonist/harmonist.git
    go: finding git.tuxfamily.org/harmonist/harmonist.git v0.3.0
    go: downloading git.tuxfamily.org/harmonist/harmonist.git v0.3.0
    

    J'ai lancé la même commande sur github à la place, ça m'a installé la version 0.4 et toutes les dépendances (cool) :

    go get -u --tags sdl github.com/anaseto/harmonist
    go: downloading github.com/anaseto/gruid v0.20.0
    go: downloading github.com/anaseto/gruid-sdl v0.1.1
    etc
    

    par contre quand je démarre harmonist-0.4 ça indique au démarrage, en haut, en rouge : "Error : gob: type mismatch in decoder: want struct type gruid. Point; got non-struct Could not load saved game… starting new game. "

    Mais si je sauvegarde le jeu ensuite je n'ai plus ça au prochain démarrage, l'état a bien été sauvegardé.

    Ensuite, au niveau de l'interface, c'est un peu dommage que la fenêtre ne soit pas redimensionnable. La fenêtre fait exactement la largeur de mon écran, et comme il n'y a pas de bordures, quand on est au bord, ça perturbe un peu de ne pas avoir de marges ou d'espace. D'ailleurs parfois il n'y a pas de murs mais on ne peut pas non plus sortir de l'espace (qui est sans doute l'espace max de jeu). De plus j'aurai aimé avoir le jeu en plein écran, là la fenêtre est deux fois plus large que haute et pas moyen (visible) de changer cela. (bon ce n'est pas bien grave non plus)

    Le coup de pouvoir sauter depuis les murs pour échapper aux ennemis est astucieux. Par contre parfois forcément quand on va plus loin que le mur ça saute quand on ne veut pas forcément. Peut-être qu'une option pour changer le comportement sera pas mal (soit "sauter tout le temps face à un mur", soit "confirmer avant de sauter", comme pour entrer dans les abysses ou je ne sais plus quoi.)

    un super jeu en tout cas, sans doute mon roguelike préféré !

    • [^] # Re: retour

      Posté par  (site Web personnel) . Évalué à 3 (+1/-0).

      Super merci pour le retour !!

      J'avoue ne pas savoir pourquoi la commande d'installation ne marche plus avec la nouvelle version en mettant le lien vers tuxfamily : je vais regarder ça, merci.

      la doc n'indique toujours pas comment compiler le projet depuis le code source télécharger.

      Tu peux le faire en lançant go install --tags sdl dans le dossier (ça téléchargera quand même les dépendances). Je mentionnerai ça dans le README, merci. D'ailleurs, à la place du -u tu peux aussi ajouter la version précise avec @v0.4.0 en fin d'url.

      par contre quand je démarre harmonist-0.4 ça indique au démarrage, en haut, en rouge : "Error : gob: type mismatch in decoder: want struct type gruid. Point; got non-struct Could not load saved game… starting new game. "

      Hm, oui, le message apparaît parce que tu as déja joué à une version précédente et que le vieux fichier de sauvegarde n'était pas compatible. J'avoue que je devrais séparer ça en deux messages : un pour le joueur, du style « Warning : could not load old save game… starting new game. » et logger les détails à part, vu que c'est pas vraiment une erreur.

      Ensuite, au niveau de l'interface, c'est un peu dommage que la fenêtre ne soit pas redimensionnable.

      La version sdl permet de zoomer avec + et -, mais comme c'est spécifique à la version sdl, j'ai zappé de documenter ça.

      D'ailleurs parfois il n'y a pas de murs mais on ne peut pas non plus sortir de l'espace (qui est sans doute l'espace max de jeu).

      Oui, le jeu utilise les dimensions 80x24, qui sont les dimensions traditionnelles du terminal et des roguelikes comme nethack. C'est un peu pour ça que la fenêtre n'est pas redimensionnable, pour éviter les confusions, tout en utilisant l'espace au maximum, sans bordures (comme nethack, du moins vanilla).

      Peut-être qu'une option pour changer le comportement sera pas mal (soit "sauter tout le temps face à un mur", soit "confirmer avant de sauter", comme pour entrer dans les abysses ou je ne sais plus quoi.)

      J'y ai pensé, mais vu que c'est une fonctionnalité très utilisée pendant le jeu, je m'étais dis qu'une demande de confirmation serait un peu lourde. Contrairement aux abysses, c'est moins dangereux comme faux-pas, mais le risque est quand même là, effectivement ;-) Mais ça serait possible d'ajouter une option, oui.

      De plus j'aurai aimé avoir le jeu en plein écran, là la fenêtre est deux fois plus large que haute et pas moyen (visible) de changer cela. (bon ce n'est pas bien grave non plus)

      Pour le coup, c'est possible avec la nouvelle option -F : harmonist -F lance le jeu en plein écran ! :-)

      • [^] # Re: retour

        Posté par  (site Web personnel) . Évalué à 3 (+1/-0).

        J'avoue ne pas savoir pourquoi la commande d'installation ne marche plus avec la nouvelle version en mettant le lien vers tuxfamily : je vais regarder ça, merci.

        Hm, j'ai testé une fois. Ça n'a pas marché non plus. J'ai retesté, puis ça a marché : peut-être un soucis temporaire sur le serveur, aucune idée.

        Sinon, je viens de traiter les trucs simples :

        Tu peux le faire en lançant go install --tags sdl dans le dossier (ça téléchargera quand même les dépendances). Je mentionnerai ça dans le README, merci.

        C'est fait. En fait, je ne mentionne plus go get, car ça n'a pas trop de sens dans un README, et ça fait quelques trucs pas intuitifs (par exemple, il nomme l'exécutable harmonist.git lorsqu'on utilise l'url de tuxfamily, ce qui est plutôt bête).

        J'avoue que je devrais séparer ça en deux messages : un pour le joueur, du style « Warning : could not load old save game… starting new game. » et logger les détails à part, vu que c'est pas vraiment une erreur.

        Corrigé aussi !

        La version sdl permet de zoomer avec + et -, mais comme c'est spécifique à la version sdl, j'ai zappé de documenter ça.

        C'est ajouté dans l'aide maintenant.

        Pour tester les changements, tu peux simplement lancer :

        git pull
        go install --tags sdl
        

        Encore merci pour les retours, c'est bien utile !

        De plus j'aurai aimé avoir le jeu en plein écran, là la fenêtre est deux fois plus large que haute et pas moyen (visible) de changer cela. (bon ce n'est pas bien grave non plus)

        Ah, par contre, c'est vrai, le plein écran d'harmonist préserve les dimensions. Je pourrais changer cela, mais les images apparaitraient alors étirées, ce qui serait un peu bizarre, non ?

        • [^] # Re: retour

          Posté par  . Évalué à 2 (+0/-0).

          oui c'est possible que si les serveurs de TF sont temporairement indisponibles, ça passe sur une version dispo (comme la 3ème dans mon cas)

          Je pourrais changer cela, mais les images apparaitraient alors étirées, ce qui serait un peu bizarre, non ?

          oui ça serait horrible, à oublier. Je viens de tester sur un écran 16/9, et effectivement ça passe mieux, la fenêtre reste au milieu de l'écran. Je pensais que si la fenêtre était plus petite, ça scrollerait dans l'affichage, je ne pensais pas que le fenêtre minimum était de 1280 de large pour la version graphique. En tout cas les graphismes apportent beaucoup, et ce que j'adore c'est que ça donne toujours l'impression de jouer en console pourtant, mais le détail des éléments est bien plus précis qu'en mode ascii.

          harmonist -F lance le jeu en plein écran ! :-)

          oui c'est bien ça

          J'y ai pensé, mais vu que c'est une fonctionnalité très utilisée pendant le jeu, je m'étais dis qu'une demande de confirmation serait un peu lourde. Contrairement aux abysses, c'est moins dangereux comme faux-pas, mais le risque est quand même là, effectivement ;-) Mais ça serait possible d'ajouter une option, oui.

          j'ai pensé autre chose, effectivement la confirmation serait lourde, et effectivement le faux pas sera moins catastrophique. Peut-être que le mieux serait de demander une confirmation en "ralentissant" l'action, mais que ça valide uniquement si on continue dans la même direction (avec les flèches de direction), et sinon si on appuie sur une autre direction ça ne fasse pas de saut. Ça permettrait peut-être d'éviter les sauts intempestif, tout en ne bloquant pas le joueur dans son flux de jeu (lui demander de taper "y" serait lourd). Concrètement ça ne ferait un saut que si on fonce 2 fois dans un mur.

          En parlant de ça, il faudrait réfléchir à comment gérer la zone hors jeu où les murs ne sont pas dessinés. On imagine que si on ne peut pas sortir, c'est parce qu'il y a un mur, du coup on devrait pouvoir sauter même si on ne voit pas le mur.

          Un exemple en image :

          Titre de l'image

          Je peux sauter sur le mur à droite, mais pas si je vais vers le haut. Ce n'est pas super logique, si je suis bloqué c'est qu'il y a forcément un mur au nord.

          D'autre part, il n'est pas possible d'aller dans toutes les directions du pavé numérique, je présume que c'est assumé.

          Ce qui veut dire que dans un cas comme celui-ci, je vais forcément mourir (sauf si j'ai un objet qui va bien), car je ne pourrais pas me sauver en appuyant sur 7 ou 9 :

          Titre de l'image

          en soit ce n'est ni logique ni illogique de fonctionner comme ça, on peut imaginer que quand on est acculé dans un couloir on n'a pas à pouvoir sortir sur les diagonales, d'un autre côté un petit singe pourrait peut-être le faire. Je me posais la question parce que certains roguelike permettent ces directions (crawl par exemple).

          • [^] # Re: retour

            Posté par  (site Web personnel) . Évalué à 3 (+1/-0). Dernière modification le 25/02/21 à 16:33.

            Peut-être que le mieux serait de demander une confirmation en "ralentissant" l'action, mais que ça valide uniquement si on continue dans la même direction (avec les flèches de direction), et sinon si on appuie sur une autre direction ça ne fasse pas de saut. Ça permettrait peut-être d'éviter les sauts intempestif, tout en ne bloquant pas le joueur dans son flux de jeu (lui demander de taper "y" serait lourd). Concrètement ça ne ferait un saut que si on fonce 2 fois dans un mur.

            On peut vouloir faire d'autres actions que bouger dans la même direction après un saut : par exemple utiliser un magara, tourner vers une autre direction, faire un saut contre un autre mur latéral, etc. C'est plus compliqué que ça en a l'air. Et puis c'est difficile de rendre un saut conditionnel intuitif, car lorsqu'on fait un saut, on veut voir normalement le résultat avant d'effectuer l'action suivante (la vue change, on peut changer de plan après le saut).

            Je peux sauter sur le mur à droite, mais pas si je vais vers le haut. Ce n'est pas super logique, si je suis bloqué c'est qu'il y a forcément un mur au nord.

            Mais s'il y a un mur au nord, il devrait être possible de creuser aussi (avec une magara de digging par exemple, ou certains types d'explosions), ce qui n'est pas possible. On pourrait imager des raisons qui empêchent de creuser mais pas de sauter, par exemple qu'il s'agit d'un mur de roche éternelle. Mais du coup on peut aussi imaginer qu'il s'agit d'une barrière énergétique naturelle de celles qu'on trouve à certains endroits des Souterrains, ce qui permettrait de justifier qu'il il n'est pas possible de prendre appuis convenablement pour sauter. Ça pourrait aussi être de la roche vampirique, par exemple. Il manque pas de trucs dans Haréka pour justifier une idée ou l'autre et, d'un point de vue gameplay, les deux ont un intérêt : empêcher de sauter contre les bords encourage à trouver d'autres solutions. Après, au final, ça reste un jeu : il sera jamais vraiment réaliste d'avoir une carte rectangulaire :-)

            D'autre part, il n'est pas possible d'aller dans toutes les directions du pavé numérique, je présume que c'est assumé.

            Oui, c'est voulu : le fait de limiter le mouvement à quatre directions permet de réaliser des manœuvres de contournement d'un monstre dans un espace ouvert sans se faire attaquer, et parfois même de se retrouver dans l'angle mort de vision d'un monstre et faire fausse piste. Ça a accessoirement aussi l'avantage d'être plus accessible aux joueurs sans pavé numérique.

            Ce qui veut dire que dans un cas comme celui-ci, je vais forcément mourir (sauf si j'ai un objet qui va bien), car je ne pourrais pas me sauver en appuyant sur 7 ou 9 :

            Tu peux sauter contre le mur derrière, ou, si c'est contre le bord, tu peux prendre appui sur le monstre en bougeant vers lui pour sauter de l'autre côté. Prendre appui sur le monstre est moins efficace que contre un mur, donc tu sautes une case en moins. Ça veut dire que le monstre a le temps de t'attaquer une fois, mais si tu as plus d'un point de vie, il est possible de s'en sortir !

            • [^] # Re: retour

              Posté par  . Évalué à 2 (+0/-0).

              d'accord, merci pour ces précisions !

              allez, j'y retourne…

              • [^] # Re: retour

                Posté par  . Évalué à 2 (+0/-0).

                un dernier petit truc, le binaire harmonist pour la version sdl, a pour titre "gruid go-sdl2", ça serait mieux si ça s'appelait "harmonist", et avait la petite icône qui va bien

                • [^] # Re: retour

                  Posté par  (site Web personnel) . Évalué à 2 (+0/-0).

                  Ah, pas faux, j'avais oublié ce détail, j'y ai pensé en écrivant la bibliothèque, mais n'y ai plus pensé par la suite : c'est l'inconvénient d'utiliser un gestionnaire de fenêtres qui n'affiche pas le nom des fenêtres. Corrigé !

                  Pour l'icône, j'imagine que ça doit être une de ces entrées .desktop, j'avoue ne rien y connaître.

                  • [^] # Re: retour

                    Posté par  . Évalué à 2 (+0/-0).

                    les fichiers .desktop sont généralement pour placer des icônes dans le menu ou sur le bureau, là je parlais plutôt de l'icône qui apparaît dans la liste du gestionnaire de fenêtres.

                    Titre de l'image

                    Pour un fichier desktop, tu peux comparer avec de l'existant, après si ça a été installé via go et non pas le gestionnaire de paquet, ça sera plus compliqué peut-être (je pense à la gestion du chemin de l'exécutable et de l'icône).

                    Ça pourrait ressembler à :

                    [Desktop Entry]
                    Type=Application
                    
                    Name=Harmonist
                    Exec=harmonist
                    Terminal=true
                    Icon=harmonist
                    Categories=Game;AdventureGame;
                    
                    • [^] # Re: retour

                      Posté par  (site Web personnel) . Évalué à 3 (+1/-0).

                      Ah, cette icône là. Je pourrais ajouter ça, oui, je vois qu'effectivement il y a une fonction pour ajouter une icône dans SDL. Je suis un peu déconnecté des environnements de bureaux traditionnels, je crois que je n'y aurais jamais pensé, merci de me l'avoir signalé :-)

                      Pour le fichier desktop, ça a l'air en effet plus le genre de trucs à ajouter par une distribution dans une installation par gestionnaire de paquets.

            • [^] # Re: retour

              Posté par  (site Web personnel) . Évalué à 3 (+1/-0).

              On peut vouloir faire d'autres actions que bouger dans la même direction après un saut : par exemple utiliser un magara, tourner vers une autre direction, faire un saut contre un autre mur latéral, etc. C'est plus compliqué que ça en a l'air. Et puis c'est difficile de rendre un saut conditionnel intuitif, car lorsqu'on fait un saut, on veut voir normalement le résultat avant d'effectuer l'action suivante (la vue change, on peut changer de plan après le saut).

              Ah, je me rends compte que j'avais pas bien compris ta suggestion. Oui, foncer deux fois dans le mur serait peut-être mieux qu'une confirmation complète avec y. Il faudrait quand même que j'affiche un truc dans les logs à chaque fois pour demander confirmation pour savoir ce qu'il faut faire, et ça serait plus difficile à expliquer.

              • [^] # Re: retour

                Posté par  . Évalué à 2 (+0/-0).

                oui c'était ça que j'avais en tête, ça ne perturberait pas trop le joueur de cette façon. Peut-être tenter de garder le comportement normal et de proposer ça en plus dans les options ?

Envoyer un commentaire

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n’en sommes pas responsables.