Création d'un système d'objets robuste

12

Mon objectif est de créer un système d'articles modulaire / aussi générique que possible qui pourrait gérer des choses comme:

  • Objets évolutifs (+6 Katana)
  • Modificateurs de statistiques (+15 dextérité)
  • Modificateurs d'objets (% X chance de faire Y dégâts, chance de geler)
  • Articles rechargeables (bâton magique avec 30 utilisations)
  • Définir les éléments (équiper 4 pièces de X pour activer la fonction Y)
  • Rareté (commune, unique, légendaire)
  • Désenchantable (s'introduit dans certains matériaux d'artisanat)
  • Fabriquable (peut être fabriqué avec certains matériaux)
  • Consommable (5 min% de puissance d'attaque X, soins +15 ch)

* J'ai pu résoudre des fonctionnalités en gras dans la configuration suivante.

Maintenant, j'ai essayé d'ajouter de nombreuses options pour refléter ce que j'avais en tête. Je ne prévois pas d'ajouter toutes ces fonctionnalités nécessaires, mais j'aimerais pouvoir les implémenter comme bon me semble. Celles-ci devraient également être compatibles avec le système d'inventaire et la sérialisation des données.

Je prévois de ne pas utiliser l'héritage du tout, mais plutôt une approche axée sur les composants d'entité / les données. Au départ, j'ai pensé à un système qui a:

  • BaseStat: une classe générique qui contient des statistiques en déplacement (peut également être utilisée pour les objets et les statistiques des personnages)
  • Élément: une classe qui contient des données telles que la liste, le nom, le type d'élément et les éléments liés à l'interface utilisateur, nom d'action, description, etc.
  • IWeapon: interface pour arme. Chaque arme aura sa propre classe avec IWeapon implémentée. Cela aura Attaque et une référence aux statistiques des personnages. Lorsque l'arme est équipée, ses données (stat de la classe d'objet) seront injectées dans la stat du personnage (quel que soit le BaseStat qu'elle possède, elles seront ajoutées à la classe de personnage en tant que bonus de stat) .Par exemple, nous voulons produire une épée (en pensant à produire des classes d'objets avec json) donc l'épée ajoutera 5 attaques aux statistiques des personnages. Nous avons donc un BaseStat as ("Attack", 5) (nous pouvons aussi utiliser enum). Cette statistique sera ajoutée à la statistique "Attaque" du personnage en tant que BonusStat (qui serait une classe différente) lors de son équipement. Donc, une classe nommée Sword implémente IWeapon sera créée quand elle 'La classe d'élément est créée. Nous pouvons donc injecter des statistiques de personnage dans cette épée et lors de l'attaque, elle peut récupérer la statistique d' attaque totale de la statistique de personnage et infliger des dégâts dans la méthode d'attaque.
  • BonusStat: est un moyen d'ajouter des statistiques sous forme de bonus sans toucher au BaseStat.
  • IConsommable: Même logique qu'avec IWeapon. L'ajout de statistiques directes est assez facile (+15 ch) mais je ne suis pas sûr d'ajouter des armes temporaires avec cette configuration (% x pour attaquer pendant 5 min).
  • IUpgradeable: Cela peut être mis en œuvre avec cette configuration. Je pense UpgradeLevel comme une statistique de base, qui est augmentée lors de la mise à niveau de l'arme. Une fois mis à niveau, nous pouvons recalculer le BaseStat de l'arme pour correspondre à son niveau de mise à niveau.

Jusqu'à ce point, je peux voir que le système est assez bon. Mais pour d'autres fonctionnalités, je pense que nous avons besoin d'autre chose, car par exemple, je ne peux pas implémenter de fonctionnalité Craftable dans ce que mon BaseStat ne serait pas en mesure de gérer cette fonctionnalité et c'est là que je suis resté coincé. Je peux ajouter tous les ingrédients en tant que Stat mais cela n'aurait aucun sens.

Pour vous faciliter la tâche, voici quelques questions auxquelles vous pouvez répondre:

  • Dois-je continuer cette configuration pour implémenter d'autres fonctionnalités? Serait-ce possible sans héritage?
  • Existe-t-il un moyen d’imaginer toutes ces fonctionnalités sans héritage?
  • À propos des modificateurs d'articles, comment pourrait-on y parvenir? Parce qu'il est très générique dans sa nature.
  • Que peut-on faire pour faciliter le processus de construction de ce type d'architecture, des recommandations?
  • Y a-t-il des sources que je peux creuser liées à ce problème?
  • J'essaie vraiment d'éviter l'héritage, mais pensez-vous que ceux-ci seraient résolus / atteints avec l'héritage avec facilité tout en le gardant assez maintenable?

N'hésitez pas à répondre à une seule question car j'ai gardé des questions très larges afin que je puisse obtenir des connaissances de différents aspects / personnes.


ÉDITER


Suite à la réponse de @ jjimenezg93, j'ai créé un système très basique en C # pour les tests, ça marche! Voyez si vous pouvez y ajouter quelque chose:

public interface IItem
{
    List<IAttribute> Components { get; set; }

    void ReceiveMessage<T>(T message);
}

public interface IAttribute
{
    IItem source { get; set; }
    void ReceiveMessage<T>(T message);
}

Jusqu'à présent, IItem et IAttribute sont des interfaces de base. Il n'était pas nécessaire (que je puisse penser) d'avoir une interface / attribut de base pour le message, nous allons donc créer directement une classe de message de test. Maintenant pour les classes de test:


public class TestItem : IItem
{
    private List<IAttribute> _components = new List<IAttribute>();
    public List<IAttribute> Components
    {
        get
        {
            return _components;
        }

        set
        {
            _components = value;
        }
    }

    public void ReceiveMessage<T>(T message)
    {
        foreach (IAttribute attribute in Components)
        {
            attribute.ReceiveMessage(message);
        }
    }
}

public class TestAttribute : IAttribute
{
    string _infoRequiredFromMessage;

    public TestAttribute(IItem source)
    {
        _source = source;
    }

    private IItem _source;
    public IItem source
    {
        get
        {
            return _source;
        }

        set
        {
            _source = value;
        }
    }

    public void ReceiveMessage<T>(T message)
    {
        TestMessage convertedMessage = message as TestMessage;
        if (convertedMessage != null)
        {
            convertedMessage.Execute();
            _infoRequiredFromMessage = convertedMessage._particularInformationThatNeedsToBePassed;
            Debug.Log("Message passed : " + _infoRequiredFromMessage);

        }
    }
} 

public class TestMessage
{
    private string _messageString;
    private int _messageInt;
    public string _particularInformationThatNeedsToBePassed;
    public TestMessage(string messageString, int messageInt, string particularInformationThatNeedsToBePassed)
    {
        _messageString = messageString;
        _messageInt = messageInt;
        _particularInformationThatNeedsToBePassed = particularInformationThatNeedsToBePassed;
    }
    //messages should not have methods, so this is here for fun and testing.
    public void Execute()
    {
        Debug.Log("Desired Execution Method: \nThis is test message : " + _messageString + "\nThis is test int : " + _messageInt);
    }
} 

Ce sont la configuration nécessaire. Nous pouvons maintenant utiliser le système (ce qui suit est pour Unity).

public class TestManager : MonoBehaviour
{

    // Use this for initialization
    void Start()
    {
        TestItem testItem = new TestItem();
        TestAttribute testAttribute = new TestAttribute(testItem);
        testItem.Components.Add(testAttribute);
        TestMessage testMessage = new TestMessage("my test message", 1, "VERYIMPORTANTINFO");
        testItem.ReceiveMessage(testMessage);
    }

}

Attachez ce script TestManager à un composant dans la scène et vous pouvez voir dans le débogage que le message est passé avec succès.


Pour expliquer les choses: chaque élément du jeu implémentera l'interface IItem et chaque attribut (le nom ne devrait pas vous confondre, cela signifie la fonctionnalité / le système de l'élément. Comme évolutif ou désenchantable) implémentera IAttribute. Ensuite, nous avons une méthode pour traiter le message (pourquoi nous avons besoin d'un message sera expliqué dans un autre exemple). Donc, dans le contexte, vous pouvez attacher des attributs à un élément et laisser le reste faire pour vous. Ce qui est très flexible, car vous pouvez facilement ajouter / supprimer des attributs. Un pseudo-exemple serait donc désenchantable. Nous aurons une classe appelée Disenchantable (IAttribute) et dans la méthode Disenchant, elle demandera:

  • Liste des ingrédients (lorsque l'article est désenchanté, quel article doit être donné au joueur) Remarque: IItem devrait être étendu pour avoir ItemType, ItemID etc.
  • int resultModifier (si vous implémentez une sorte de boost de la fonction de désenchantement, vous pouvez passer un int ici pour augmenter les ingrédients reçus en cas de désenchantement)
  • int failureChance (si le processus de désenchantement a une chance d'échec)

etc.

Ces informations seront fournies par une classe appelée DisenchantManager, elle recevra l'élément et formera ce message en fonction de l'élément (ingrédients de l'élément lorsqu'il est désenchanté) et de la progression du joueur (resultModifier et failureChance). Afin de transmettre ce message, nous allons créer une classe DisenchantMessage, qui agira comme un corps pour ce message. Ainsi, DisenchantManager remplira un DisenchantMessage et l'enverra à l'élément. L'élément recevra le message et le transmettra à tous ses attributs attachés. Étant donné que la méthode ReceiveMessage de la classe Disenchantable recherchera un DisenchantMessage, seul l'attribut Disenchantable recevra ce message et agira en conséquence. J'espère que cela clarifie les choses autant que pour moi :).

Vandarthul
la source
1
Vous pouvez trouver une inspiration utile dans les systèmes de modificateurs utilisés dans For Honor
DMGregory
@DMGregory Hé! Merci pour le lien. Bien que cela semble très ingénieux, j'ai malheureusement besoin de la conversation pour saisir le concept. Et j'essaie de découvrir la conversation réelle, qui est malheureusement le contenu réservé aux membres de GDCVault (495 $ pour un an est fou!). (Vous pouvez trouver la conférence ici si vous êtes membre de GDCVault -, -
Vandarthul
Comment, exactement, votre concept "BaseStat" empêcherait-il les armes à fabriquer?
Attackfarm
Cela n'empêche pas vraiment, mais ne correspond pas vraiment au contexte dans mon esprit. Il est possible d'ajouter "Wood", 2 et "Iron", 5 comme BaseStat à une recette d'artisanat, ce qui donnerait l'épée. Je pense que changer le nom de BaseStat en BaseAttribute servirait mieux dans ce contexte. Mais encore, le système ne sera pas en mesure de remplir son objectif. Pensez aux consommables avec 5 min -% 50 de puissance d'attaque. Comment pourrais-je le passer en tant que BaseStat? "Perc_AttackPower", 50 cela doit être résolu comme "si c'est Perc, traitez l'entier comme un pourcentage" et manquez les informations des minutes. J'espère que vous comprenez ce que je veux dire.
Vandarthul
@Attackfarm après réflexion, ce concept "BaseStat" peut être étendu avec une liste d'entiers au lieu d'un seul int. donc, pour le buff consommable, je peux fournir "Attack", 50, 5, 1 et IConsumable chercherait 3 entiers, 1. - valeur, 2. - minutes, 3. - si c'est un pourcentage ou non. Mais cela semble impulsif alors que d'autres systèmes entrent et sont obligés de s'expliquer uniquement en int.
Vandarthul du

Réponses:

6

Je pense que vous pouvez réaliser ce que vous voulez en termes d'évolutivité et de maintenabilité en utilisant un système Entity-Component avec un héritage de base et un système de messagerie. Bien sûr, gardez à l'esprit que ce système est le plus modulaire / personnalisable / évolutif auquel je puisse penser, mais il fonctionnera probablement moins bien que votre solution actuelle.

J'expliquerai plus loin:

Tout d'abord, vous créez une interface IItemet une interface IComponent. Tout élément que vous souhaitez stocker doit hériter IItemet tout composant dont vous souhaitez affecter vos éléments doit hériter IComponent.

IItemaura un tableau de composants et une méthode de manipulation IMessage. Cette méthode de gestion envoie simplement tout message reçu à tous les composants stockés. Ensuite, les composants intéressés par ce message donné agiront en conséquence et les autres l'ignoreront.

Un message, par exemple, est de type dommages, et il informe à la fois l'attaquant et l'attaquant, vous savez donc combien vous frappez et peut-être chargez votre barre de fureur en fonction de ces dégâts. Ou l'IA ennemie peut décider de courir si elle vous frappe et fait moins de 2 HP de dégâts. Ce sont des exemples stupides, mais en utilisant un système similaire à celui que je mentionne, vous n'aurez rien d'autre à faire que de créer un message et les manipulations appropriées pour ajouter la plupart de ce type de mécanique.

J'ai une mise en œuvre d'un ECS avec messagerie ici , mais ceci est utilisé pour les entités au lieu des objets et utilise C ++. Quoi qu'il en soit, je pense que cela peut aider si vous jetez un oeil à component.h, entity.het messages.h. Il y a beaucoup de choses à améliorer, mais cela a fonctionné pour moi dans ce travail universitaire simple.

J'espère que cela aide.

jjimenezg93
la source
Hé @ jjimenezg93, merci pour votre réponse. Donc, pour élaborer avec un exemple simple de ce que vous avez expliqué: Nous voulons une épée qui soit: - Désenchantable [Composant] - Modificateur de statistiques [Composant] - Améliorable [Composant] J'ai une liste d'actions qui contient toutes choses comme - DISENCHANT - MODIFY_STAT - UPGRADE Chaque fois que l'élément reçoit ce message, passe par tous ses composants et envoie ce message, chaque composant saura quoi faire avec le message donné. En théorie, cela semble génial! Je n'ai pas vérifié votre exemple mais je le ferai, merci beaucoup!
Vandarthul
@Vandarthul Oui, c'est essentiellement l'idée. De cette façon, l'élément ne saura rien de ses composants, il n'y a donc pas de couplage du tout et, en même temps, il aura toutes les fonctionnalités que vous désirez, qui peuvent également être partagées entre différents types d'éléments. J'espère que cela correspond à vos besoins!
jjimenezg93