9 avr. 2024

Game dev #01: he protecc, he attacc, but most importantly he shouldn't dive in enemies groups like an idiot

Salutations, voyageur !

"Je rêve de coder un RPG maison." pense-t-elle, son imposant postérieur vissé dans son siège de bureau effiloché. "Un jeu où on gère des villageois procéduraux, et on leur confie des tâches pour que tout soit autonome et satisfaisant, et on pourrait aussi développer des relations avec ceux qu'on aime bien."

Et la malotrue décide, tout naturellement, de commencer par le combat. LE COMBAT. Avec une logique pareille, je pourrais être à deux doigts de commander un plat asiatique en me targuant d'apprendre le mandarin, ou de lécher des blocs de sel pour progresser en sculpture de pierre.

Les raisons qui m'ont motivée à commencer cette aventure de programmation par la bagar, sont doubles.

Premièrement, n'étant pas à mon premier petit projet de dev, je savais qu'il me fallait changer de stratégie de travail pour tenir sur la durée. Dés le début de ce développement, j'avais commencé à coder une génération de carte procédurale, avec rivière et biomes, et à y mapper les bons meshs et les bonnes textures. Avant de me perdre en effusion d'efforts, pour afficher de sombres modèles de pissenlit, en affichant ou cachant la floraison à l'aide d'un shader fait-main.

 
 De l'herbe que l'on écrase, et de l'eau qu'on remue. N'êtes-vous pas immensément divertis ?

C'est au milieu d'une bataille éprouvante avec les pétales d'une plante, que j'ai lâché mon clavier, pour réfléchir quelques minutes à ce que je faisais. J'étais encore en train de tout mélanger, code, game design, idées hasardeuses et création artistique, et d’essouffler mon envie de continuer sur le projet aussi rapidement qu'un rat éclate un pain au sésame frais, laissé dans la poubelle dehors.

"ARET" me suis-je dis, avec une voix digne de Kermit la grenouille. Cette fois-ci, construit un vrai prototype. Fais un vrai jeu, avec des visuels poubelles, des cubes gris et des indications visuelles minimales pour développer petit à petit un projet qui fonctionne, sans graphismes. Ce n'est pas faute de trouver ce conseil de protoyping à la première page de n'importe quel forum, ou tutoriel de développement de jeu vidéo.

Et je dois dire que la technique fonctionne diablement bien. J'ai repris le projet, en le mettant à nu, et en me concentrant exclusivement sur le code. Cela me permet maintenant de gérer au poil les mécaniques, le feeling et le game design des systèmes. Sans parler de la clarté du code, que j'ai pu factoriser et nettoyer en classes proprement héritées pour éviter tout redondance.


Mon arborescence actuelle de classe pour permettre une programmation propre, et qui ne se répète jamais entre les différentes entités du jeu. Ouais, je sais, je flex sur un principe de base de la programmation orientée objet. Je travaille en GdScript, alors ce n'est pas ici que vous trouverez le moindre soupçon de honte MOUHAHAHA-

Godot, comme tout langage orienté objet, encourage le développement de systèmes de jeu les plus indépendants et autonomes possibles entre eux. Et quel meilleur exemple, comme mécanique indépendante, que le combat ? Mes créatures sont sur la carte. Peuvent-elles se battre ? Oui, non ? Dans tous les cas, une fois les hostilités passées, ce qu'elles feront de leur temps n'aura plus rien à voir avec ce pan de gameplay.

 

 
Une caméra, un personnage-joueur, ou des ennemis qui s'initialisent correctement, et indépendamment de la scène où ils sont instanciés. Je jurerai que ce genre de plaisir de psychopathe est l'un de mes moteurs pour coder ce jeu.

 

La seconde raison qui m'a motivée à commencer par les mécaniques de combat, c'est qu'elles doivent rester simples. Et par doivent, j'entends qu'elles doivent se faire violence pour rester accessibles et engageantes. Dans les jeux vidéos, j'aime mes batailles comme je souhaiterai que soient mes déclarations d’impôts: intuitives et satisfaisantes. 

 

 
En tout cas, je maîtrise la chasse au barroth alpha beaucoup mieux que mon espace URSSAF.

Je n'aurais pas peur de rendre les premiers combats de Lampyre extrêmement simples à engager, et très plaisants à voir se dérouler, pour en augmenter la difficulté plus tard dans la partie ou la campagne (j'ai déjà les 10 premières minutes de jeu parfaitement planifiées ! zOMG ! mais je vous les présenterai dans un article futur, une fois que le concept de corruption émergera dans le code). 

C'est aussi pour cela que j'ai choisi un gameplay d'auto-attacker (comprenez, pour les francophones les plus retords, d'attaque automatique). Si ce mode d'attaque bride une partie de l'expression de skill du joueur, il est tout de même très adapté à l'élaboration de builds procéduraux, et à la présence de personnages non-joueurs alliés autour de votre protagoniste. 

 

De gauche à droite, et de haut en bas : Vampire Survivor, Brotato ou Deep Rock Galactic: Survivor, qui vous invitent à accumuler les modifications pour tondre des pelletés de monstres par attaques automatiques. Et Dragon Age Inquisition, Final Fantasy 12 et Dragon's Dogma 2, qui ajoutent des compagnons d'arme contrôlés par l'ordinateur à votre équipe. Et si on ajoutait un peu de construction et de gestion de villageois à votre panier, madame ?


En outre, ce système d'auto-attaque simplifie également l'aspect programmation, puisque les créatures obéissent à un comportement plutôt scripté lorsqu'elle sont en posture hostile ou défensives.

Mouvement : les créatures fuient une cible, se maintiennent au corps à corps, à distance ou essaient de garder une proximité relative avec les membres de leur groupe (le cas des Vyrrlins associés en escouade).

Perception et gestion des ennemis : la portée de détection est représentée par une surface arbitraire autour des entités, où celles-ci scannent les autres créatures grâce aux signaux entrants et sortants de leur Area2D ; elles déterminent si c'est un ennemi, maintiennent une liste de leurs adversaires proches, et décident si elles doivent l'attaquer selon leur situation actuelle. La cible prioritaire est pour l'instant la plus proche, rafraichie toutes les 0.2 secondes pour éviter un calcul de distance peu optimisé à chaque frame.

Les ennemis sont détectés par le joueur lorsqu'ils entre sa zone de "perception" (ici le collider étendu autour du joueur). Si la créature ne retient pas ses coups, elle considère la cible la plus proche comme son ennemi principal et se tourne vers elle. Si elle se rapproche sous sa portée de coup, elle le frappe.

 

Attaque prioritaire : l'arme principale est utilisée automatiquement en mêlée, à distance ou en projection de zone de dégâts (voir ci-dessous).

Utilisation passive de compétences et d'esquive : à chaque attaque ennemie, les créatures les plus agiles ont une chance d'esquive totale ou partielle des dégâts. Une esquive réussie draine l'énergie (seconde ressource après les points de vie). Cela doit empêcher un ennemi rapide d'esquiver trop d'attaques dans le cas où il affronte plusieurs opposants.

Certains ennemis, et vyrrlins, possèdent des compétences passives qui renforcent leurs attaques (dédoublement, application de poison, gain de vitesse d'attaque), mais aussi des compétences "actives" se lançant périodiquement (une plus grosse attaque toutes les X secondes). Le terme "actif" est entre parenthèses, car le joueur ne pourra demander à ses alliés qu'une priorisation dans le lancement des effets.

Le résultat est un joueur qui peut se déplacer, désengager le combat et esquiver une attaque de zone manuellement, en sautant en dehors de son périmètre. Le reste est automatique et se lance dés que les ennemis sont à portée. Les alliés seront efficaces et autonomes, utilisant au maximum leurs compétences dés qu'elles sont disponibles, dans l'ordre prévu. Cela peu paraitre complexe, mais les compétences restent toujours relativement simples, et les types de dégâts limités. Le joueur devra essentiellement gérer les armes utilisées par ses alliés, la composition de leur groupe, et les capacités qu'ils utilisent au combat.

Je n'ai pour l'instant pas prévu de possibilité de donner l'ordre de concentrer les attaques sur une seule cible. J'aimerai développer des combats qui n'en demandent pas l'utilité, car un de mes buts est de permettre au joueur d'envoyer des escouades autonomes détruire des autels de corruption. Mais si cela parait au final trop frustrant, j'en implémenterai la possibilité.


Trois grands types d'attaques valent pour toutes les créatures. Elles utilisent toutes la même classe Weapon et la même logique de détection (AKA, quand la logique d'attaque d'une chimère de deux mètres est programmée, on pourra la décliner instantanément pour celle du canard sauvage).

Les attaque de corps à corps, qui sont une attaque à portée courte mais surtout sans projectile voyageant vers la cible.


Les attaques à distance, avec un effet voyageant vers la cible et conservant sa rotation. Techniquement, les dégâts se déclenchent au départ du projectile, tout comme le calcul de l'évitement. Lorsque la flèche part, elle "sait" déjà si elle va toucher ou non.


Les attaques de zone sont codées sur la même base, mais au lieu d'infliger immédiatement des dégâts à une cible vivante, elles créent une zone avec un collider à l'emplacement visé. Ce collider a deux fonctions : prévenir les créatures ennemies prises à l'intérieur, pour les notifier qu'elles doivent l'esquiver si possible d'un bond immédiat, ou à minima l'éviter comme obstacle de pathfinding. Deuxièmement, leur infliger des dégâts et/ou des effets une fois son expiration arrivée.


 

Sur ce second GIF, on peut voir que les ennemis pris dans le rayon prévu de l'attaque tentent immédiatement d'en sortir d'un bond rapide (dash), avec plus ou moins de succès. La zone jaune apparaissant et disparaissant en même temps que la zone rouge de l'attaque représente une zone obstacle au pathfinding des ennemis, les faisant l'éviter tant qu'elle n'a pas encore déclenché ses dégâts. Oh, et on ne peux pas tirer à travers les murs. Essentiel.

Ce délai d'expiration peut représenter le temps que met un projectile enflammé à rejoindre sa cible ou sol, ou le temps que met un animal qui charge devant lui. La reconnaissance de l'appartenance d'une attaque est déjà implémentée. Une créature sait, en étant dans le rayon de cette attaque, si elle va lui infliger des dégâts ou non (pas de friendly fire, ce serait trop chaotique et peu satisfaisant à gérer !).

Pour nuancer ce caractère robotique, des aspects aléatoires interviennent dans ces comportements de combat. J'ai déjà mentionné les esquives d'attaque ciblée, ou bien l'évitement d'une zone de dégâts d'un bond (un évitement limité car très drainant en énergie, mais pouvant atténuer l'efficacité de lanceurs de feu dans votre équipe par exemple, ou au contraire permettre à vos villageois d'éviter une charge). Des statistiques affecteront aussi, de façon notable, l'efficacité de certaines armes (spoiler, les montagnes de muscle frapperont plus fort, mais pas d'inquiétude - chacun trouvera son compte au combat).

Trois types d'ennemis ombreux sont actuellement prévus, ainsi que plusieurs espèces animales qui auront pour certaines un ou deux compétences passives à leur actif pour pimenter leurs combats.

Si le développement de toutes ces mécaniques est en très bonne voie pour le moment, il me reste à implémenter certains statuts (dégâts de poison, saignement, mise à terre) ainsi que l'arbre de talents des vyrrlins, une étape intimidante !

Je vise une mise en place quasi intégrale du gameplay combat, en arène, avant de passer aux autres aspects du jeu. Si j'arrive à rendre le spectacle de sphères se battant procéduralement contre des vagues de cubes plaisant, il n'y aura définitivement plus aucun autre aspect du jeu qui m'effraiera pour la suite du projet.

Artistiquement parlant, même si ce n'est pas pour tout de suite, j'espère mélanger animations semi-procédurales et divisées selon le haut et le bas du corps, pour donner un visuel fluide où les créatures se suivent et "dansent" les unes par rapport aux autres pour donner des affrontement organiques (mais pas trop bordéliques, espérons-le).

Et voici pour le premier article de game dev ! Il y aura davantage de détail sur les combats au prochain billet (et aux suivants), lorsque les mouvements seront définitivement codés. Et d'autres sujets plus généraux, traités entre-temps. 
 
En attendant, portez vous bien !

Aucun commentaire: