Comment puis-je gérer proprement et élégamment les données et les dépendances entre les classes

12

Je travaille sur le jeu 2d topdown dans SFML 2, et j'ai besoin de trouver une manière élégante dont tout fonctionnera et s'emboîtera.

Permettez-moi de vous expliquer. J'ai un certain nombre de classes qui héritent d'une base abstraite qui fournit une méthode de dessin et une méthode de mise à jour à toutes les classes.

Dans la boucle du jeu, j'appelle update et puis dessine sur chaque classe, j'imagine que c'est une approche assez courante. J'ai des classes pour les tuiles, les collisions, le joueur et un gestionnaire de ressources qui contient toutes les tuiles / images / textures. En raison de la façon dont l'entrée fonctionne dans SFML, j'ai décidé que chaque classe gère l'entrée (si nécessaire) dans son appel de mise à jour.

Jusqu'à présent, je passais dans les dépendances au besoin, par exemple, dans la classe de joueur quand une touche de mouvement est enfoncée, j'appelle une méthode sur la classe de collision pour vérifier si la position vers laquelle le joueur veut se déplacer sera une collision, et ne déplacez le joueur que s'il n'y a pas de collision.

Cela fonctionne très bien pour la plupart, mais je crois que cela peut être mieux fait, je ne sais pas comment.

J'ai maintenant des choses plus complexes à mettre en œuvre, par exemple: un joueur peut marcher jusqu'à un objet au sol, appuyer sur une touche pour le ramasser / le piller et il apparaîtra ensuite dans l'inventaire. Cela signifie que certaines choses doivent se produire:

  • Vérifiez si le joueur est à portée d'un objet lootable sur la touche, sinon ne continuez pas.
  • Trouvez l'article.
  • Mettez à jour la texture de l'image-objet sur l'élément de sa texture par défaut à une texture "pillée".
  • Mettez à jour la collision de l'élément: il a peut-être changé de forme ou a été complètement supprimé.
  • L'inventaire doit être mis à jour avec l'article ajouté.

Comment puis-je tout faire communiquer? Avec mon système actuel, je vais finir avec mes classes hors de portée et les appels de méthode les uns aux autres partout. Je pourrais lier toutes les classes dans un seul grand manager et donner à chacune une référence à la classe des parents managers, mais cela ne semble que légèrement mieux.

Toute aide / conseil serait grandement apprécié! Si quelque chose n'est pas clair, je suis heureux de développer les choses.

Néophyte
la source
1
Vous voudrez peut-être considérer la composition ici, plutôt que l'héritage. Jetez un œil aux exemples de composition et cela pourrait vous donner quelques idées. L'idiome de bouton peut également aider à trier les choses.
OriginalDaemon
5
Un des articles canon sur la composition: Faites évoluer votre hiérarchie
doppelgreener
Semble trop localisé. Essayez le Code Review SE?
Anko

Réponses:

5

Je ne sais pas si la composition résoudra tous les problèmes. Peut-être peut-être partiellement aider. Mais si ce que vous voulez est de découpler les classes, j'examinerais davantage la logique basée sur les événements. De cette façon, par exemple, vous aurez la fonction OnLoot qui doit avoir la position du joueur et des informations sur les butins disponibles pour trouver le plus proche. Ensuite, la fonction envoie un événement à l'élément pillé. L'élément pillé dans son cycle de traitement d'événement gère cet événement de sorte que l'élément n'a besoin que de savoir comment se mettre à jour. La fonction OnLoot peut également mettre à jour l'inventaire du joueur ou l'élément lui-même peut envoyer l' événement updateInventory / * OnLootSucess * et le joueur / l'inventaire le traitera dans son propre cycle d'événements de processus.

Avantages: vous avez découplé certaines de vos classes

Inconvénients: classes d'événements ajoutées, surcharge de code peut-être inutile.

Voici l' une des façons possibles de voir à quoi cela peut ressembler:

case LOOT_KEY:
   OnLoot(PLayer->getPos(), &inventoryItems);
....

// note onLoot do not needs to know anything about InvItem class (forward decl in enough)
int onLoot(vec3 pos, InvItems& pitems)
{
    InvItem* pitem = findInRange(pos, pitems, LOOT_RANGE);
    if(pitem)
     EventManager::Instance->post( Event::makeLootEvent(pitem));
}
....

// knows only about EventManager
InvItem::processEvents()
{
    while(!events.empty())
    {
        Event* pev = events.pop();
        ...
        case LOOT_EVENT:
            // in case you broadcasted it, but better is to sort all posted/sent events and add them only if they addressed to particular item 
            if(pev->item == this && handleLoot((LootEvent)pev))
            {
                EventManager::Instance->post(Event::makeLootSuccessEvent(this));
            }
    }
}

int handleLoot(LootEvent* plootev)
{
    InvItem* pi = plootev->item;
    if(pi->canLoot())
    {
        updateTexture(pi->icon, LOOTED_ICON_RES);
        return true;
    }
    return false; 
}


...
// knows only LootSuccessEvent and player
Inventory::processEvents()
{
    while(!events.empty())
    {
        Event* pev = events.pop();
        ...
        case LOOT_SUCCESS_EVENT:
             player->GetInventory()->add( ((LootSuccessEvent*)pev)->item );
        ...
}

Ce n'est là qu'un des moyens possibles. Vous n'avez probablement pas besoin d'autant d'événements. Et je suis sûr que vous pouvez mieux connaître vos données, ce n'est que l'une des nombreuses façons.

alariq
la source