Jak mogę w czysty i elegancki sposób obsługiwać dane i zależności między klasami

12

Pracuję nad 2d topdown w SFML 2 i muszę znaleźć elegancki sposób, w jaki wszystko będzie działać i pasować do siebie.

Pozwól mi wyjaśnić. Mam szereg klas, które dziedziczą z abstrakcyjnej bazy, która zapewnia metodę rysowania i metodę aktualizacji dla wszystkich klas.

W pętli gry wywołuję aktualizację, a następnie korzystam z każdej klasy. Wyobrażam sobie, że jest to dość powszechne podejście. Mam zajęcia z kafelków, kolizji, odtwarzacza i menedżera zasobów, który zawiera wszystkie kafelki / obrazy / tekstury. Ze względu na sposób, w jaki wejście działa w SFML, postanowiłem, że każda klasa będzie obsługiwać dane wejściowe (jeśli są wymagane) w swoim wywołaniu aktualizacji.

Do tej pory przechodziłem w zależności od potrzeb, na przykład w klasie gracza po naciśnięciu klawisza ruchu, wywołuję metodę na klasie kolizji, aby sprawdzić, czy pozycja, na którą gracz chce się przenieść, będzie kolizją, i poruszaj odtwarzaczem tylko wtedy, gdy nie ma kolizji.

W większości działa to dobrze, ale wierzę, że można to zrobić lepiej, po prostu nie jestem pewien, jak to zrobić.

Mam teraz bardziej złożone rzeczy, które muszę zaimplementować, np .: gracz może podejść do obiektu na ziemi, nacisnąć klawisz, aby go podnieść / zrabować, a następnie pojawi się w ekwipunku. Oznacza to, że musi się zdarzyć kilka rzeczy:

  • Sprawdź, czy gracz znajduje się w zasięgu przedmiotu do zdobycia po naciśnięciu klawisza, w przeciwnym razie nie kontynuuj.
  • Znajdź przedmiot.
  • Zaktualizuj teksturę duszka na elemencie z domyślnej tekstury do tekstury „zrabowanej”.
  • Zaktualizuj kolizję dla elementu: mógł on zmienić kształt lub zostać całkowicie usunięty.
  • Zapas musi zostać zaktualizowany o dodany element.

Jak sprawić, by wszystko się komunikowało? W moim obecnym systemie skończy się to, że moje klasy będą poza zasięgiem, a metody będą do siebie nawzajem wszędzie. Mógłbym powiązać wszystkie klasy w jednym dużym menedżerze i dać każdemu odniesienie do klasy menedżera nadrzędnego, ale wydaje się to tylko nieco lepsze.

Każda pomoc / rada będzie bardzo mile widziana! Jeśli coś jest niejasne, cieszę się, że mogę coś rozwinąć.

Neofita
źródło
1
Warto rozważyć tutaj kompozycję zamiast dziedziczenia. Rozejrzyj się za przykładami kompozycji, które mogą dać ci pomysły. Idiom pimpl może również pomóc w uporządkowaniu rzeczy.
OriginalDaemon,
5
Jeden z kanonicznych artykułów na temat składu: Evolve your Hierarchy
doppelgreener
Wydaje się zbyt zlokalizowany. Wypróbuj Code Review SE?
Anko,

Odpowiedzi:

5

Nie jestem pewien, czy kompozycja rozwiąże wszystkie problemy. Może może częściowo pomóc. Ale jeśli chcesz oddzielić klasy, przyjrzałbym się logice opartej na zdarzeniach. W ten sposób np. Będziesz mieć funkcję OnLoot, która musi mieć dostępną pozycję gracza i informacje o łupach, aby znaleźć najbliższego. Następnie funkcja wysyła zdarzenie do zrabowanego przedmiotu. Zrabowany przedmiot w cyklu procesu zdarzenia obsługuje to zdarzenie, więc element musi tylko wiedzieć, jak się zaktualizować. Funkcja OnLoot może również aktualizować ekwipunek odtwarzacza lub sam przedmiot może wysyłać aktualizację Inventory / * OnLootSucess * zdarzenie, a odtwarzacz / ekwipunek zajmie się nim we własnym cyklu zdarzeń procesowych.

Plusy: oddzieliłeś część swoich klas

Minusy: dodano klasy zdarzeń, być może niepotrzebny narzut kodu.

Oto jeden z możliwych sposobów, jak może to wyglądać:

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

To tylko jeden z możliwych sposobów. Prawdopodobnie nie potrzebujesz tak wielu wydarzeń. I jestem pewien, że możesz lepiej poznać swoje dane, to tylko jeden z wielu sposobów.

alariq
źródło