Wie kann ich sauber und elegant mit Daten und Abhängigkeiten zwischen Klassen umgehen?

12

Ich arbeite an einem 2D-Topdown-Spiel in SFML 2 und muss eine elegante Methode finden, mit der alles funktioniert und zusammenpasst.

Lassen Sie mich das erklären. Ich habe eine Reihe von Klassen, die von einer abstrakten Basis erben, die eine Zeichenmethode und eine Aktualisierungsmethode für alle Klassen bereitstellt.

In der Spieleschleife rufe ich update auf und greife dann auf jede Klasse zurück. Ich stelle mir vor, dass dies ein ziemlich üblicher Ansatz ist. Ich habe Klassen für Kacheln, Kollisionen, den Spieler und einen Ressourcenmanager, der alle Kacheln / Bilder / Texturen enthält. Aufgrund der Art und Weise, wie die Eingabe in SFML funktioniert, habe ich mich dafür entschieden, jede Klassenhandle-Eingabe (falls erforderlich) in den Aktualisierungsaufruf aufzunehmen.

Bisher habe ich Abhängigkeiten nach Bedarf übergeben, z. B. in der Spielerklasse, wenn eine Bewegungstaste gedrückt wurde. Ich rufe eine Methode für die Kollisionsklasse auf, um zu überprüfen, ob die Position, zu der sich der Spieler bewegen möchte, eine Kollision ist. und bewege den Spieler nur, wenn es keine Kollision gibt.

Dies funktioniert größtenteils gut, aber ich glaube, es kann besser gemacht werden, ich bin mir nur nicht sicher, wie.

Ich muss jetzt komplexere Dinge implementieren, z. B .: Ein Spieler kann auf ein Objekt auf dem Boden zugehen, eine Taste drücken, um es aufzunehmen / zu plündern, und es wird dann im Inventar angezeigt. Dies bedeutet, dass ein paar Dinge passieren müssen:

  • Überprüfen Sie, ob sich der Spieler auf Tastendruck in Reichweite eines plünderbaren Gegenstands befindet, andernfalls fahren Sie nicht fort.
  • Finde den Gegenstand.
  • Aktualisieren Sie die Sprite-Textur des Elements von der Standardtextur in eine "geplünderte" Textur.
  • Aktualisieren Sie die Kollision für das Objekt: Möglicherweise hat sich die Form geändert oder es wurde vollständig entfernt.
  • Das Inventar muss mit dem hinzugefügten Artikel aktualisiert werden.

Wie kann ich alles kommunizieren lassen? Mit meinem derzeitigen System werde ich am Ende meine Klassen verlassen und überall Methodenaufrufe aneinander richten. Ich könnte alle Klassen in einem großen Manager zusammenfassen und jedem einen Verweis auf die Eltern-Manager-Klasse geben, aber das scheint nur geringfügig besser zu sein.

Jede Hilfe / Beratung wäre sehr dankbar! Wenn etwas unklar ist, möchte ich gerne etwas näher darauf eingehen.

Neophyt
quelle
1
Möglicherweise möchten Sie hier eher die Komposition als die Vererbung berücksichtigen. Schauen Sie sich nach Kompositionsbeispielen um und es könnte Ihnen einige Ideen geben. Das Pimpl-Idiom könnte auch beim Aussortieren helfen.
OriginalDaemon
5
Einer der Canon-Artikel zur Komposition: Evolve your Hierarchy
doppelgreener
Scheint zu lokalisiert. Probieren Sie die Code Review SE?
Anko

Antworten:

5

Ich bin nicht sicher, ob die Komposition alle Probleme lösen wird. Vielleicht kann das teilweise helfen. Aber wenn Sie Klassen entkoppeln möchten, würde ich mehr ereignisgesteuerte Logik untersuchen. Auf diese Weise haben Sie zB die OnLoot- Funktion, die die Position des Spielers und Informationen über die Beute benötigt, um die nächstgelegene zu finden. Dann sendet die Funktion ein Ereignis an den geplünderten Gegenstand. Geplünderter Gegenstand in seinem Ereignisprozesszyklus behandelt dieses Ereignis, sodass der Gegenstand nur wissen muss, wie er sich selbst aktualisiert. Die OnLoot- Funktion kann auch das Player-Inventar aktualisieren oder das Item selbst kann das updateInventory / * OnLootSucess * -Ereignis senden, und der Player / das Inventar verarbeitet es in seinem eigenen Prozessereigniszyklus.

Vorteile: Sie haben einige Ihrer Klassen entkoppelt

Nachteile: Event-Klassen hinzugefügt, möglicherweise nicht benötigter Code-Overhead.

Hier ist eine der möglichen Arten, wie es aussehen könnte:

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 );
        ...
}

Dies ist nur einer der möglichen Wege. Wahrscheinlich brauchen Sie nicht so viele Veranstaltungen. Und ich bin sicher, dass Sie Ihre Daten besser kennenlernen können. Dies ist nur eine von vielen Möglichkeiten.

alariq
quelle