Vue d'ensemble:
Un grand nombre de jeux avec des statistiques de type RPG permettent des améliorations du personnage allant du simple "Infliger 25% de dégâts supplémentaires" à des choses plus complexes telles que "Infliger 15 points de dégâts aux attaquants lorsqu'ils sont touchés".
Les spécificités de chaque type de buff ne sont pas vraiment pertinentes. Je cherche un moyen (vraisemblablement orienté objet) pour gérer les buffs arbitraires.
Détails:
Dans mon cas particulier, j'ai plusieurs personnages dans un environnement de combat au tour par tour, alors j'ai imaginé que les buffs soient liés à des événements tels que "OnTurnStart", "OnReceiveDamage", etc. Chaque buff est peut-être une sous-classe d'une classe abstraite principale de Buff, où seuls les événements pertinents sont surchargés. Ensuite, chaque personnage pourrait avoir un vecteur de buffs actuellement appliqués.
Cette solution a-t-elle un sens? Je peux certes constater que des dizaines de types d’événements sont nécessaires, c’est comme si créer une nouvelle sous-classe pour chaque buff était exagéré, et cela ne semblait pas autoriser d’interactions. Autrement dit, si je voulais imposer un plafond sur les augmentations de dégâts de sorte que, même si vous aviez 10 améliorations différentes qui infligent toutes 25% de dégâts supplémentaires, vous ne payez que 100% de plus, au lieu de 250% de plus.
Et il y a des situations plus compliquées que je pourrais idéalement contrôler. Je suis sûr que tout le monde peut trouver des exemples de la façon dont des passionnés plus sophistiqués peuvent potentiellement interagir les uns avec les autres d'une manière que je ne souhaite peut-être pas en tant que développeur de jeux.
En tant que programmeur C ++ relativement inexpérimenté (j'ai généralement utilisé le C dans des systèmes embarqués), j'ai l'impression que ma solution est simpliste et ne tire probablement pas pleinement parti du langage orienté objet.
Pensées? Quelqu'un a-t-il déjà conçu un système de buff relativement robuste?
Edit: Concernant la réponse (s):
J'ai choisi une réponse basée principalement sur de bons détails et une réponse solide à la question que j'ai posée, mais la lecture des réponses m'a donné un peu plus de perspicacité.
Peut-être sans surprise, les différents systèmes ou systèmes modifiés semblent mieux s’appliquer à certaines situations. Le système qui fonctionne le mieux pour mon jeu dépend des types, de la variance et du nombre d'améliorations que je compte pouvoir appliquer.
Pour un jeu comme Diablo 3 (mentionné ci-dessous), où presque n'importe quel équipement peut changer la force d'un buff, les buffs ne sont que des statistiques sur les personnages . Il semble que ce soit une bonne idée chaque fois que cela est possible.
Dans la situation au tour par tour dans laquelle je me trouve, l'approche basée sur les événements peut être plus appropriée.
Dans tous les cas, j'espère toujours que quelqu'un me proposera une balle magique "OO" qui me permettra d'appliquer une amélioration de +2 à la distance de déplacement par tour , un montant équivalent à 50% des dégâts infligés à l'attaquant , et une téléportation automatique vers une tuile proche lorsqu’elle est attaquée à partir de 3 tuiles ou plus dans un même système sans transformer un buff de force de +5 en sa propre sous-classe.
Je pense que la chose la plus proche est la réponse que j'ai marquée, mais la parole est toujours ouverte. Merci à tous pour la contribution.
la source
Réponses:
C'est une question compliquée, parce que vous parlez de différentes choses qui (ces jours-ci) sont regroupées sous le nom de «buffs»:
J'implémente toujours le premier avec une liste d'effets actifs pour un personnage donné. Le retrait de la liste, qu'il soit basé sur la durée ou explicitement, est assez simple, donc je ne couvrirai pas cela ici. Chaque effet contient une liste de modificateurs d'attribut que vous pouvez appliquer à la valeur sous-jacente via une simple multiplication.
Ensuite, je l'enveloppe avec des fonctions pour accéder aux attributs modifiés. par exemple.:
Cela vous permet d'appliquer assez facilement des effets multiplicatifs. Si vous avez également besoin d'effets additifs, déterminez l'ordre dans lequel vous souhaitez les appliquer (probablement additif en dernier) et parcourez la liste deux fois. (J'aurais probablement des listes de modificateurs distinctes dans Effect, une pour les multiplicatifs et une pour les additifs).
La valeur du critère est de vous permettre d'appliquer "+20% vs Undead" - définissez la valeur UNDEAD sur l'effet et ne transmettez la valeur UNDEAD que
get_current_attribute_value()
lorsque vous calculez un jet de dégâts contre un ennemi mort-vivant.Incidemment, je ne serais pas tenté d'essayer d'écrire un système qui applique et n'applique pas directement les valeurs à la valeur d'attribut sous-jacent - le résultat final est que vos attributs risquent fort de s'éloigner de la valeur voulue en raison d'une erreur. (Par exemple, si vous multipliez quelque chose par 2, puis que vous le coiffiez, lorsque vous le divisez à nouveau par 2, il sera plus bas que celui avec lequel il a commencé.)
En ce qui concerne les effets basés sur des événements, tels que "Inflige 15 points de dégâts en retour aux attaquants lorsqu'ils sont touchés", vous pouvez ajouter des méthodes à cette classe. Mais si vous souhaitez un comportement distinct et arbitraire (par exemple, certains effets de l'événement ci-dessus peuvent refléter des dommages, certains pourraient vous soigner, ils pourraient vous téléporter au hasard, peu importe), vous aurez besoin de fonctions ou de classes personnalisées pour le gérer. Vous pouvez attribuer des fonctions aux gestionnaires d'événements sur l'effet, puis simplement appeler les gestionnaires d'événements sur tous les effets actifs.
Il est évident que votre classe d'effets aura un gestionnaire d'événements pour chaque type d'événement et vous pouvez affecter des fonctions de gestionnaire à autant de personnes que vous le souhaitez dans chaque cas. Vous n'avez pas besoin de sous-classer Effect, car chacun est défini par la composition des modificateurs d'attribut et des gestionnaires d'événements qu'il contient. (Il contiendra probablement aussi un nom, une durée, etc.)
la source
Dans un jeu sur lequel j'ai travaillé avec un ami pour une classe, nous avons créé un système de buff / debuff lorsque l'utilisateur est pris au piège dans les hautes herbes et les tuiles rapides, entre autres choses, et quelques petites choses comme les saignements et les poisons.
L'idée était simple et bien que nous l'appliquions en Python, elle était plutôt efficace.
Voici comment cela s'est passé:
Maintenant, comment appliquer les améliorations du monde est une autre histoire. Voici ma matière à réflexion cependant.
la source
Je ne sais pas si vous lisez encore cela, mais cela fait longtemps que je lutte avec ce type de problème.
J'ai conçu de nombreux types de systèmes affectifs. Je vais les passer brièvement en revue maintenant. Tout est basé sur mon expérience. Je ne prétends pas connaître toutes les réponses.
Modificateurs statiques
Ce type de système repose principalement sur des entiers simples pour déterminer les modifications éventuelles. Par exemple, +100 à Max HP, +10 à attaquer et ainsi de suite. Ce système pourrait également gérer des pourcentages. Vous devez juste vous assurer que l'empilement ne devient pas incontrôlable.
Je n'ai jamais vraiment mis en cache les valeurs générées pour ce type de système. Par exemple, si je voulais afficher le maximum de santé de quelque chose, je générerais la valeur sur place. Cela a empêché les choses d'être sujettes aux erreurs et tout simplement plus faciles à comprendre pour toutes les personnes impliquées.
(Je travaille en Java, donc ce qui suit est basé sur Java, mais il devrait fonctionner avec quelques modifications pour d’autres langages). Ce système peut facilement être installé en utilisant des énumérations pour les types de modification, puis des entiers. Le résultat final peut être placé dans une sorte de collection qui a des paires clé / valeur ordonnées. Ce sera une recherche rapide et des calculs, donc la performance est très bonne.
Globalement, cela fonctionne très bien avec des modificateurs statiques à plat. Toutefois, le code doit exister aux emplacements appropriés pour que les modificateurs soient utilisés: getAttack, getMaxHP, getMeleeDamage, etc., etc.
Lorsque cette méthode échoue (pour moi), il existe une interaction très complexe entre les buffs. Il n’ya pas de moyen facile d’interagir autrement qu’en ghettant un peu. Il y a quelques possibilités d'interaction simples. Pour ce faire, vous devez modifier la façon dont vous stockez les modificateurs statiques. Au lieu d'utiliser une énumération comme clé, vous utilisez une chaîne. Cette chaîne serait le nom Enum + variable supplémentaire. 9 fois sur 10, la variable supplémentaire n'est pas utilisée, vous conservez donc le nom enum comme clé.
Prenons un exemple rapide: si vous voulez pouvoir modifier les dégâts infligés aux créatures morts-vivants, vous pourriez avoir une paire ordonnée comme ceci: (DAMAGE_Undead, 10) Le DAMAGE est l’énum et le Mort-vivant est la variable supplémentaire. Donc, pendant votre combat, vous pouvez faire quelque chose comme:
Quoi qu'il en soit, cela fonctionne assez bien et est rapide. Mais cela échoue aux interactions complexes et à un code «spécial» partout. Par exemple, considérons la situation de «25% de chances de se téléporter à la mort». Ceci est un "assez" complexe. Le système ci-dessus peut le gérer, mais pas facilement, car vous avez besoin des éléments suivants:
Donc cela m'amène à mon prochain:
Le système ultime de buff complexe
Une fois, j'ai essayé d'écrire moi-même un MMORPG 2D. C'était une terrible erreur mais j'ai beaucoup appris!
J'ai réécrit le système affect 3 fois. Le premier utilisait une variante moins puissante de ce qui précède. Le second était ce que je vais parler.
Ce système avait une série de classes pour chaque modification. Des choses comme: ChangeHP, ChangeMaxHP, ChangeHPByPercent, ChangeMaxByPercent. J'ai eu un million de ces gars - même des choses comme TeleportOnDeath.
Mes cours avaient des choses qui feraient ce qui suit:
Appliquer et supprimer expliquer eux-mêmes (bien que pour des pourcentages tels que, par exemple, l’effet garderait une trace de son augmentation de HP pour assurer que lorsque l’effet disparaîtrait, il ne supprime que le montant ajouté. Il m'a fallu beaucoup de temps pour m'assurer que tout allait bien. Je n'ai toujours pas eu un bon pressentiment à ce sujet.).
La méthode checkForInteraction était un morceau de code incroyablement complexe. Dans chacune des classes d’effets (c.-à-d. ChangeHP), il y aurait un code pour déterminer s’il doit être modifié par l’effet d’entrée. Donc, par exemple, si vous aviez quelque chose comme ...
La méthode checkForInteraction traiterait tous ces effets. Pour ce faire, chacun des effets sur TOUS les joueurs à proximité devait être vérifié! En effet, le type d’effets que j’avais eu avec plusieurs joueurs sur une zone donnée. Cela signifie que le code N'A JAMAIS eu de déclaration spéciale comme ci-dessus - "si nous venons de mourir, nous devrions vérifier le téléport au décès". Ce système le traiterait automatiquement correctement au bon moment.
Essayer d'écrire ce système m'a pris environ deux mois et m'a fait exploser à plusieurs reprises. CEPENDANT, il était VRAIMENT puissant et pouvait faire une quantité incroyable de choses - en particulier si vous tenez compte des deux faits suivants pour les capacités de mon jeu: 1. Ils avaient des domaines cibles (c.-à-d. Unique, seul, groupe uniquement, PB AE). , Cible PB AE, AE ciblée, etc.). 2. Les capacités pourraient avoir plus d'un effet sur elles.
Comme je l'ai mentionné ci-dessus, il s'agissait du deuxième système de troisième affect pour ce jeu. Pourquoi je me suis éloigné de ça?
Ce système a eu la pire performance que j'ai jamais vue! C'était terriblement lent, car il devait faire beaucoup de vérifications pour chaque chose qui se passait. J'ai essayé de l'améliorer, mais j'ai considéré que c'était un échec.
Nous arrivons donc à ma troisième version (et à un autre type de système de buff):
Classe d'affect complexe avec gestionnaires
Il s’agit donc en fait d’une combinaison des deux premières: nous pouvons avoir des variables statiques dans une classe Affect contenant de nombreuses fonctionnalités et des données supplémentaires. Ensuite, il suffit d'appeler des gestionnaires (pour moi, des méthodes utilitaires statiques plutôt que des sous-classes pour des actions spécifiques. Mais je suis sûr que vous pouvez utiliser des sous-classes pour des actions si vous le souhaitez également) lorsque nous voulons faire quelque chose.
La classe Affect aurait toutes les bonnes choses juteuses, comme les types de cibles, la durée, le nombre d'utilisations, les chances d'exécution et ainsi de suite.
Il faudrait encore ajouter des codes spéciaux pour gérer les situations, par exemple, le téléport à la mort. Nous devrions toujours vérifier cela dans le code de combat manuellement, et si cela existait, nous aurions une liste des affects. Cette liste d’effets contient tous les effets actuellement appliqués sur le joueur qui s’est déjà téléporté à sa mort. Ensuite, nous examinerions chacun d’entre eux et vérifierions s’il s’exécutait et réussissait (nous nous arrêterions au premier essai réussi). Si cela réussissait, nous appelions simplement le gestionnaire pour s’occuper de cela.
L'interaction peut être faite, si vous voulez aussi. Il suffirait d’écrire le code pour rechercher des améliorations spécifiques sur les lecteurs / etc. Parce qu'il a de bonnes performances (voir ci-dessous), il devrait être assez efficace pour le faire. Il aurait simplement besoin de gestionnaires plus complexes, etc.
Donc, il a beaucoup de performances du premier système et toujours beaucoup de complexité comme le second (mais pas autant). Au moins en Java, vous pouvez faire certaines choses difficiles pour obtenir les performances de presque la première dans la plupart des cas (par exemple, avoir une carte enum ( http://docs.oracle.com/javase/6/docs/api/java). /util/EnumMap.html ) avec Enums comme clés et ArrayList des affects en tant que valeurs, ce qui vous permet de voir si vous avez rapidement des affects [puisque la liste serait 0 ou que la carte n'aurait pas l'énum] et ne pas avoir parcourir sans cesse les listes d’effets des joueurs sans aucune raison (cela ne me dérange pas d’itérer plus d’affect si nous en avons besoin à ce moment-là. J'optimiserai plus tard si cela devient un problème).
Je suis actuellement en train de ré-ouvrir (réécrire le jeu en Java au lieu du code de base FastROM dans lequel il se trouvait à l'origine) mon MUD qui s'est terminé en 2005 et j'ai récemment compris comment je veux implémenter mon système de buff. Je vais utiliser ce système car il a bien fonctionné dans mon jeu précédent échoué.
Espérons que quelqu'un, quelque part, trouvera certaines de ces idées utiles.
la source
Une classe (ou fonction adressable) différente pour chaque buff n'est pas excessive si le comportement de ces buff est différent les uns des autres. Une chose serait d'avoir des améliorations de + 10% ou de + 20% (qui, bien sûr, seraient mieux représentées comme deux objets de la même classe), une autre implémenterait des effets extrêmement différents qui nécessiteraient de toute façon un code personnalisé. Cependant, je pense qu'il vaut mieux avoir moyens standard de personnalisation de la logique de jeu plutôt que de laisser chaque buff faire ce qu'il veut (et d'interférer les uns avec les autres de manière imprévue, en perturbant l'équilibre du jeu).
Je suggérerais de diviser chaque "cycle d'attaque" en étapes, chaque étape ayant une valeur de base, une liste ordonnée de modifications pouvant être appliquées à cette valeur (éventuellement limitée) et une limite finale. Chaque modification a une transformation d'identité par défaut et peut être influencée par zéro ou plus buffs / debuffs. Les détails de chaque modification dépendraient de l’étape appliquée. La manière dont le cycle est mis en œuvre dépend de vous (y compris l'option d'une architecture basée sur les événements, comme vous en avez parlé).
Un exemple de cycle d'attaque pourrait être:
Il est important de noter que plus un buff est appliqué tôt dans le cycle, plus il aura d’effet sur le résultat . Donc, si vous voulez un combat plus "tactique" (où la compétence du joueur est plus importante que le niveau du personnage), créez beaucoup de buffs / debuffs sur les statistiques de base. Si vous souhaitez un combat plus "équilibré" (où le niveau importe plus - il est important dans les MMOG de limiter le taux de progression), utilisez uniquement les buffs / debuffs plus tard dans le cycle.
La distinction entre "Modifications" et "Améliorations" dont j'ai parlé plus tôt a un but: les décisions concernant les règles et l'équilibre peuvent être appliquées à la première, de sorte que toute modification apportée à celles-ci n'a pas besoin de refléter les modifications apportées à chaque classe de la dernière. OTOH, le nombre et le type de buffs ne sont limités que par votre imagination, chacun pouvant exprimer le comportement souhaité sans avoir à prendre en compte une éventuelle interaction entre lui et les autres (ni même l'existence des autres).
Donc, pour répondre à la question: ne créez pas de classe pour chaque buff, mais une classe pour chaque modification (type de), et liez la modification au cycle d'attaque, pas au personnage. Les buffs peuvent être simplement une liste de tuples (Modification, clé, valeur), et vous pouvez appliquer un buff à un personnage en ajoutant / supprimant simplement ce dernier dans son ensemble de buffs. Cela réduit également la fenêtre d'erreur, car les statistiques du personnage n'ont pas besoin d'être modifiées du tout lorsque les buffs sont appliqués (il y a donc moins de risque de restaurer une statistique à une valeur incorrecte après l'expiration d'un buff).
la source
Je ne sais pas si vous le lisez encore, mais voici comment je le fais maintenant (le code est basé sur UE4 et C ++). Après avoir réfléchi au problème pendant plus de deux semaines (!!), j'ai finalement trouvé ceci:
http://gamedevelopment.tutsplus.com/tutorials/using-the-composite-design-pattern-for-an-rpg-attributes-system--gamedev-243
Et je me suis dit que bien, encapsuler un seul attribut au sein de la classe / structure n’est pas une si mauvaise idée après tout. Gardez cependant à l'esprit que je tire un très grand avantage du système de réflexion de code intégré à UE4. Par conséquent, sans quelques retouches, cela pourrait ne pas convenir partout.
De toute façon, j'ai commencé à encapsuler l'attribut dans une seule structure:
Ce n'est toujours pas fini mais l'idée de base est que cette structure garde une trace de son état interne. Les attributs ne peuvent être modifiés que par effets. Essayer de les modifier directement est dangereux et n’est pas exposé aux concepteurs. Je suppose que tout ce qui peut interagir avec les attributs est un effet. Y compris les bonus plats des articles. Lorsqu'un nouvel élément est équipé, un nouvel effet (avec la poignée) est créé et ajouté à la carte dédiée, qui gère les bonus de durée infinie (ceux qui doivent être supprimés manuellement par le joueur). Lorsqu'un nouvel effet est appliqué, un nouveau descripteur est créé (le descripteur est juste int, enveloppé avec une structure), puis ce descripteur est transmis de manière généralisée afin d'interagir avec cet effet, ainsi que le suivi de l'effet. toujours actif. Lorsque l'effet est supprimé, son traitement est diffusé à tous les objets intéressés,
La vraie partie importante de ceci est TMap (TMap est une carte hachée). FGAModifier est une structure très simple:
Il contient le type de modification:
Et Valeur qui est la valeur finale calculée que nous allons appliquer à l’attribut.
Nous ajoutons un nouvel effet en utilisant une fonction simple, puis appelons:
Cette fonction est supposée recalculer toute la pile de bonus, chaque fois qu'un effet est ajouté ou supprimé. La fonction n'est toujours pas terminée (comme vous pouvez le voir), mais vous pouvez en avoir une idée générale.
Mon plus gros problème en ce moment est la gestion de l'attribut Damaging / Healing (sans impliquer de recalculer toute la pile), je pense avoir résolu ce problème, mais cela nécessite toujours plus de tests pour être à 100%.
Dans tous les cas, les attributs sont définis comme suit (+ macros Unreal, omis ici):
etc.
De plus, je ne suis pas sûr à 100% de la gestion de CurrentValue d'attribut, mais cela devrait fonctionner. Ils sont comme ça maintenant.
Quoi qu’il en soit, j’espère que cela sauvera certaines personnes de la mémoire cache, sans savoir si c’est la meilleure solution, ni même une bonne solution, mais je l’aime plus que le suivi des effets indépendamment des attributs. Rendre chaque attribut dépistant son propre état est beaucoup plus facile dans ce cas, et devrait être moins sujet aux erreurs. Il n'y a essentiellement qu'un seul point d'échec, qui est une classe assez courte et simple.
la source
J'ai travaillé sur un petit MMO et tous les objets, pouvoirs, buffs, etc. avaient des "effets". Un effet était une classe ayant des variables pour 'AddDefense', 'InstantDamage', 'HealHP', etc. Les pouvoirs, les éléments, etc. gèrent la durée de cet effet.
Lorsque vous lancez un pouvoir ou placez un objet, il applique l'effet au personnage pour la durée spécifiée. Ensuite, l’attaque principale, les calculs, etc. prendraient en compte les effets appliqués.
Par exemple, vous avez un buff qui ajoute une défense. Il y aurait au minimum un EffectID et une Durée pour ce buff. Lors de sa conversion, l’effet EffectID s’appliquerait au caractère pour la durée spécifiée.
Un autre exemple pour un article aurait les mêmes champs. Mais la durée serait infinie ou jusqu'à ce que l'effet soit supprimé en enlevant l'élément du personnage.
Cette méthode vous permet de parcourir une liste d’effets actuellement appliqués.
J'espère que j'ai expliqué cette méthode assez clairement.
la source
J'utilise ScriptableOjects en tant que buffs / sorts / talents
using UnityEngine; using System.Collections.Generic;
public enum BuffType {Buff, Debuff} [System.Serializable] classe publique BuffStat {public Stat Stat = Stat.Strength; flottant public ModValueInPercent = 0.1f; }
BuffModul:
la source
C'était une question réelle pour moi. J'ai une idée à ce sujet.
Buff
liste et un programme de mise à jour logique pour les buffs.Buff
classe.De cette manière, il peut être facile d’ajouter de nouvelles statistiques de joueur, sans aucun changement dans la logique des
Buff
sous - classes.la source
Je sais que c’est assez vieux, mais c’est lié à un post plus récent et j’aimerais y réfléchir. Malheureusement, je n'ai pas mes notes avec moi pour le moment, je vais donc essayer de vous donner un aperçu général de ce dont je parle et de modifier les détails ainsi qu'un exemple de code lorsque je l'aurai devant vous. moi.
Premièrement, je pense que du point de vue de la conception, la plupart des gens sont trop pris au piège des types de buffs pouvant être créés et de la façon dont ils sont appliqués, en oubliant les principes de base de la programmation orientée objet.
Qu'est ce que je veux dire? Peu importe qu’il s’agisse d’un buff ou d’un debuff, ce sont deux modificateurs qui affectent quelque chose de manière positive ou négative. Le code ne tient pas compte de qui est qui. D'ailleurs, peu importe si quelque chose ajoute des statistiques ou les multiplie, ce ne sont que des opérateurs différents et, là encore, le code n'a pas d'importance.
Alors, où vais-je avec ça? Il n’est pas si difficile de concevoir une bonne classe de buff / debuff (lire: simple et élégante). Ce qui est difficile, c’est de concevoir les systèmes qui calculent et maintiennent l’état du jeu.
Si je concevais un système buff / debuff, voici quelques points à considérer:
Quelques détails sur les types de buff / debuff qui doivent contenir:
Ce n'est qu'un début, mais à partir de là, vous définissez simplement ce que vous voulez et agissez en fonction de votre état de jeu normal. Par exemple, supposons que vous vouliez créer un objet maudit qui réduise la vitesse de déplacement ...
Tant que j'ai mis les types appropriés en place, il est simple de créer un enregistrement de buff qui dit:
Et ainsi de suite, et quand je crée un buff, je lui attribue simplement le BuffType of Curse et tout le reste appartient au moteur ...
la source