paint-brush
Un système de capacités plus complexe, mais un flux de travail Gamedev plus fluide – Comment ?par@pastaman
1,150 lectures
1,150 lectures

Un système de capacités plus complexe, mais un flux de travail Gamedev plus fluide – Comment ?

par Vladimir Popov9m2024/02/08
Read on Terminal Reader

Trop long; Pour lire

Le système de capacités de War Robots a subi une évolution significative, passant d'une structure simpliste à un modèle sophistiqué. Le nouveau système responsabilise les concepteurs de jeux et réduit le recours aux programmeurs, ce qui se traduit par des capacités mécaniques plus diversifiées et des expériences de joueur enrichies.
featured image - Un système de capacités plus complexe, mais un flux de travail Gamedev plus fluide – Comment ?
Vladimir Popov HackerNoon profile picture


Bonjour! Je m'appelle Vladimir Popov, développeur client sur le projet War Robots. Au moment de la rédaction de cet article, War Robots existe depuis plusieurs années et des dizaines de nouveaux robots sont apparus dans le jeu pendant cette période. Naturellement, les capacités variées des robots sont importantes, car sans elles, les robots perdent leur caractère unique, ce qui rend le jeu plus intéressant.


Dans cet article, je vais partager comment le système de capacités de jeu fonctionne dans notre jeu – et comment il a évolué. Et, pour rendre les choses plus accessibles, j'expliquerai les choses en termes simples et sans trop de détails techniques.


Comment les capacités étaient mises en œuvre

Tout d’abord, plongeons dans l’historique du projet et examinons une implémentation plus ancienne qui n’est plus utilisée.


Auparavant, les capacités étaient conçues de manière très triviale : elles comportaient un composant attaché au robot. Il s'agissait d'une construction dans laquelle le programmeur décrivait en détail le fonctionnement de la capacité : à la fois son flux et la manière dont elle interagit avec d'autres capacités. Toute la logique est décrite dans un seul composant, et le concepteur du jeu peut simplement l'attacher au robot et configurer les paramètres selon ses besoins. Il convient de mentionner qu'il n'était pas possible de modifier le flux des capacités – les concepteurs du jeu ne pouvaient modifier que les paramètres et les timings.


Une ancienne capacité ne pouvait exister que dans deux états : actif et inactif, et chaque état pouvait se voir attribuer son action.




Regardons un exemple de la capacité « Jammer », que possédait auparavant le robot « Stalker » ; ça a fonctionné comme ça :


  1. Si la capacité est active, une animation s'affiche et le robot entre dans l'état Jammer ; dans cet état, d’autres ne peuvent pas viser le robot qui active la capacité.
  2. Si la capacité est inactive, rien ne se passe.
  3. Lorsque nous essayions d'activer une capacité, nous vérifiions si plus de n secondes s'étaient écoulées depuis la dernière activation.
  4. La désactivation s'est produite automatiquement après m secondes.


Pendant longtemps, cette fonctionnalité nous suffisait, mais au fil du temps, les concepteurs de jeux et les programmeurs n'étaient plus satisfaits de cette approche : il était difficile pour les programmeurs de supporter ces capacités car le code était devenu monstrueux ; cela impliquait une très longue chaîne d’héritage, où chaque situation devait être décrite. De plus, les concepteurs de jeux manquaient de flexibilité : pour apporter des modifications à une capacité, ils devaient ordonner des modifications aux programmeurs, même si une capacité adjacente avait la même fonctionnalité.


Entrez dans le nouveau système de capacités

Nous avons donc réalisé que quelque chose devait changer. Nous avons donc développé un nouveau système dans lequel chaque capacité était représentée comme un ensemble de plusieurs objets liés. La fonctionnalité a été divisée en capacités d'état et en composants d'état.


Comment ça marche? Chaque capacité a un objet principal. Cet objet central relie d’autres objets de capacité au monde extérieur, et vice versa ; il prend également toutes les décisions importantes.


Il peut y avoir n'importe quel nombre d'états. Essentiellement, l'état dans cette itération n'est pas très différent des états actif/inactif de l'ancienne version, mais il peut désormais y en avoir un nombre illimité et leur objectif est devenu plus abstrait. On notera qu'une capacité ne peut avoir qu'un seul état actif à la fois.


La principale innovation par rapport à l'ancien système concernait les composants : un composant décrit une action, et chaque État peut avoir un nombre illimité de composants.


Comment fonctionnent les nouvelles capacités ? Une capacité ne peut être que dans l’un des états ; l'objet principal est responsable de leur commutation. Les composants liés à un état réagissent à l’activation/désactivation de l’état et, en fonction de cela, peuvent soit commencer à effectuer une action, soit cesser de l’exécuter.


Tous les objets sont devenus personnalisables ; un concepteur de jeu peut mélanger les états et les composants à sa guise, composant ainsi une nouvelle capacité à partir de blocs préinstallés. Désormais, les programmeurs n'ont plus qu'à entrer dans l'image pour créer un nouveau composant ou un nouvel état, ce qui rend l'écriture de code beaucoup plus facile : ils travaillent avec de petites entités, décrivent quelques éléments simples et ne construisent plus la capacité eux-mêmes – les concepteurs de jeux le font désormais.


Le flux est devenu comme ceci :

  1. L'objet principal active le premier état
  2. L'État active toutes ses composantes
  3. L'état détermine le moment où la capacité passe à un autre état
  4. L'objet principal désactive l'état précédent
  5. L'état précédent désactive ses composants
  6. L'objet principal active un nouvel état
  7. Le nouvel État active ses composantes


Par la suite, cette procédure est répétée encore et encore. Pour faciliter l'utilisation, un état sert non seulement de conteneur de composants, il détermine également quand passer à un autre état et demande à l'objet principal d'effectuer le changement. Au fil du temps, cela s'est avéré toujours insuffisant pour nous, et le diagramme de capacités s'est transformé comme suit :




L'objet principal, l'état et les composants sont restés à leur place, mais de nouveaux éléments ont également été ajoutés.


La première chose qui attire votre attention est que nous avons ajouté des conditions à chaque État et composant : pour les États, celles-ci définissent des exigences supplémentaires pour quitter l'État ; pour les composants, ils déterminent si le composant peut effectuer son action.


Le conteneur de charges contient des charges, les recharge, arrête la recharge si nécessaire et fournit des charges que les États peuvent utiliser.


Un timer est utilisé lorsque plusieurs états doivent avoir un temps d’exécution commun, mais que leur propre temps d’exécution n’est pas défini.


Il est important de noter que tous les objets de capacité sont facultatifs. Techniquement, pour pouvoir travailler, il suffit d’un objet principal et d’un seul état.


Aujourd'hui, même s'il n'existe pas vraiment beaucoup de capacités entièrement construites sans l'implication des programmeurs, le développement en général est devenu sensiblement moins cher, car les programmeurs n'ont désormais plus qu'à écrire de très petites choses : par exemple, un nouvel état ou deux composants – le reste. est réutilisé.


Résumons les composantes de nos capacités :


  • L' objet principal remplit les fonctions d'une machine à états. Il fournit aux États et aux composants des informations sur le monde et fournit au monde des informations sur les capacités. L'objet principal sert de lien entre les états, les composants et les parties de service de la capacité : charges et minuteries externes.


  • L' état écoute les commandes d'activation et de désactivation de l'objet principal et, en conséquence, active et désactive les composants, et demande également à l'objet principal de passer à un autre état. L'État détermine quand il doit passer au suivant ; pour ce faire, il utilise sa condition interne : si le joueur a cliqué sur le bouton de capacité, si un certain temps s'est écoulé depuis l'activation de l'état, etc., et des conditions externes liées à l'état.


  • Le composant écoute les commandes d'activation et de désactivation de l'état et effectue certaines actions : discrètes ou à long terme. Les actions peuvent être complètement différentes : elles peuvent causer des dégâts, soigner un allié, activer une animation, etc.


  • La condition vérifie l'état dans lequel se trouve l'élément souhaité et le signale à l'état ou au composant. Les conditions peuvent être complexes. Un État ne demande pas de transition vers un autre État si la condition n'est pas remplie. Le composant n’effectue pas non plus d’action si la condition n’est pas remplie. Les conditions sont une entité facultative ; toutes les capacités ne les possèdent pas.


  • Le conteneur de charges retient les charges, les recharge, arrête la recharge lorsque cela est nécessaire et fournit des charges aux États. Il est utilisé dans les capacités multi-charges, lorsque vous devez permettre au joueur de l'utiliser plusieurs fois, mais pas plus de n fois de suite.


  • La minuterie est utilisée lorsque plusieurs états ont une durée commune, mais on ne sait pas combien de temps durera chacun d'eux. N'importe quel état peut démarrer un minuteur pendant n secondes. Tous les états concernés s'abonnent à l'événement de fin de minuterie et font quelque chose lorsqu'il se termine.


Revenons maintenant au diagramme de capacités. Comment sa fonctionnalité a-t-elle changé ?


  1. Au début du jeu, l'objet principal sélectionne le premier état et l'active
  2. L'État active toutes ses composantes
  3. Le composant vérifie si la condition est remplie et exécute ensuite seulement une action
  4. L'État commence à vérifier les conditions de transition vers un autre État
  5. Si la condition est remplie et que la condition supplémentaire qui y est liée est satisfaite, l'état demande à l'objet principal de passer dans un autre état.
  6. L'objet principal désactive cet état et en active un autre
  7. Toute la procédure est répétée


Les États peuvent utiliser les redevances comme condition de transition supplémentaire. Si une telle transition se produit, le nombre de charges diminue. Les États peuvent également utiliser une minuterie commune. Dans ce cas, la durée totale de leur exécution sera déterminée par une minuterie, et chaque état individuellement peut durer à tout moment.


Interfaces utilisateur de capacités

Nous n'avons pas complètement réinventé la roue pour les nouvelles interfaces utilisateur de capacités.


L'objet principal possède sa propre interface utilisateur. Il définit certains éléments qui doivent toujours être dans l'interface utilisateur et qui ne dépendent pas de l'état actuellement actif.


Chaque état possède sa propre paire dans l'interface utilisateur, et l'interface utilisateur de l'état s'affiche uniquement lorsque son état est actif. Il reçoit des données sur son état et peut les afficher d'une manière ou d'une autre. Par exemple, les états de durée ont généralement une barre et un texte dans leur interface utilisateur qui affiche le temps restant.


Dans le cas où l'État attend une commande externe pour poursuivre une capacité, son interface utilisateur affiche un bouton et une pression dessus envoie la commande à l'État.





Exemples de capacités

Nous verrons comment fonctionnent les capacités à l'aide d'exemples spécifiques ; Tout d'abord, regardons un robot appelé « Inquisiteur ». Nous avons quatre états qui se succèdent – au-dessus des états, vous pouvez voir leur affichage dans l'interface utilisateur. Pour deux d’entre eux, on voit aussi les composants qui leur appartiennent ; les deux autres États n’ont tout simplement pas de composants.


Voici le flux de la capacité :

  1. Tout commence par l'état « WaitForClick ». À ce stade, la capacité ne fait rien ; il attend juste les commandes.


  2. Dès qu'une telle commande est reçue, l'objet principal change d'état. Le prochain état actif est « WaitForGrounded ».


  3. Cet état comporte certains composants et, par conséquent, lorsqu'il est activé, le robot saute et joue un son et une animation. Entre autres choses, tant que l'état est actif, le robot est affecté par l'effet Jammer, qui interdit de viser le robot.


  4. Lorsque le robot atterrit, sa capacité passe à l’état suivant.


  5. Cet état comporte trois composants : le son et le brouilleur déjà familiers, ainsi que le tremblement, qui fait trembler la caméra pour tous les joueurs dans un rayon de n .


  6. Puisque cet état a une Durée , il fonctionne pendant n secondes, puis la capacité passe à l'état suivant.


  7. Le dernier état est également livré avec une durée, mais il n'a aucun composant : il a un temps de recharge régulier.


  8. Une fois terminé, la capacité revient au premier état.





Un autre exemple est « Phantom ». Cela ressemble beaucoup à Inquisitor, mais il y a quelques nuances :


  1. Nous commençons par WaitForClick.


  2. Ensuite, la durée pendant laquelle la téléportation est installée, les statistiques du robot sont modifiées et le son et l'animation sont joués.


  3. Après cela : DurationOrClick, dans lequel les statistiques du robot sont modifiées, l'animation et les effets sont joués.


  4. Si un clic a été effectué, nous passons à une autre durée, pendant laquelle le robot se téléporte, les statistiques changent et l'animation, les effets et les sons sont joués.


  5. Après cet état (ou après l'expiration du délai pour DurationOrClick), nous passons à Duration.


La principale différence ici est que nous voyons des états avec des branchements : DurationOrClick passe à l'état A si le temps spécifié est écoulé, ou à l'état B si le joueur a déjà appuyé sur le bouton de capacité.



Conclusions

Même s’il semblerait que notre système ait évolué de quelque chose de simple à quelque chose d’assez complexe, ce changement a simplifié la vie des programmeurs et des concepteurs de jeux. L'assistance des programmeurs est désormais principalement nécessaire lors de l'ajout de petits composants, tandis que ce dernier groupe de membres de l'équipe a acquis une plus grande autonomie et peut désormais assembler indépendamment de nouvelles capacités à partir d'états et de composants existants. Comme autre bonus, dans le même temps, les joueurs ont également reçu un bénéfice sous la forme de capacités plus diverses et plus complexes des mechs.