Come posso gestire in modo pulito ed elegante i dati e le dipendenze tra le classi


12

Sto lavorando al secondo gioco topdown in SFML 2 e ho bisogno di trovare un modo elegante in cui tutto funzionerà e si adatterà insieme.

Mi permetta di spiegare. Ho un numero di classi che ereditano da una base astratta che fornisce un metodo di disegno e un metodo di aggiornamento a tutte le classi.

Nel loop del gioco, chiamo aggiornamento e quindi disegno su ogni classe, immagino che questo sia un approccio piuttosto comune. Ho lezioni per tessere, collisioni, il giocatore e un gestore delle risorse che contiene tutte le tessere / immagini / trame. A causa del modo in cui l'input funziona in SFML ho deciso di fare in modo che ogni classe gestisca l'input (se richiesto) nella sua chiamata di aggiornamento.

Fino ad ora ho passato dipendenze secondo necessità, ad esempio nella classe del giocatore quando viene premuto un tasto di movimento, chiamo un metodo sulla classe di collisione per verificare se la posizione in cui il giocatore vuole spostarsi sarà una collisione, e muovere il giocatore solo in assenza di collisione.

Funziona bene per la maggior parte, ma credo che possa essere fatto meglio, non sono sicuro di come.

Ora ho cose più complesse che devo implementare, ad esempio: un giocatore è in grado di camminare su un oggetto a terra, premere un tasto per raccoglierlo / saccheggiarlo e poi verrà mostrato nell'inventario. Ciò significa che devono accadere alcune cose:

  • Controlla se il giocatore si trova nel raggio di un oggetto bottino premendo il tasto, altrimenti non procedere.
  • Trova l'oggetto.
  • Aggiorna la trama sprite sull'elemento dalla sua trama predefinita a una trama "saccheggiata".
  • Aggiorna la collisione per l'oggetto: potrebbe aver cambiato forma o essere stato rimosso completamente.
  • L'inventario deve essere aggiornato con l'articolo aggiunto.

Come faccio a comunicare tutto? Con il mio sistema attuale finirò con le mie lezioni che vanno al di fuori del campo di applicazione e le chiamate di metodo tra loro ovunque. Potrei legare tutte le classi in un unico grande manager e dare a ciascuno un riferimento alla classe del gestore principale, ma questo sembra solo leggermente migliore.

Qualsiasi aiuto / consiglio sarebbe molto apprezzato! Se qualcosa non è chiaro, sono felice di espandermi sulle cose.


1
Potresti prendere in considerazione la composizione qui, piuttosto che l'eredità. Dai un'occhiata agli esempi di composizione e potrebbe darti alcune idee. Il linguaggio del brufolo potrebbe aiutare anche a sistemare le cose.
OriginalDaemon

5
Uno degli articoli canonici sulla composizione: fai evolvere la tua gerarchia
doppelgreener

Sembra troppo localizzato. Prova la recensione del codice SE?
Anko,

Risposte:


5

Non sono sicuro se la composizione risolverà tutti i problemi. Forse può parzialmente aiutare. Ma se ciò che vuoi è disaccoppiare le classi, esaminerei più la logica guidata dagli eventi. In questo modo, ad esempio, avrai la funzione OnLoot che deve avere la posizione del giocatore e informazioni sui bottini disponibili per trovare il più vicino. Quindi la funzione invia l'evento all'elemento saccheggiato. L'articolo saccheggiato nel suo ciclo di processo degli eventi gestisce questo evento, quindi l'elemento deve solo sapere come aggiornarsi. La funzione OnLoot può anche aggiornare l'inventario del giocatore o l'oggetto stesso può inviare l' evento updateInventory / * OnLootSucess * e il giocatore / inventario lo gestirà nel proprio ciclo di eventi di processo.

Pro: hai disaccoppiato alcune delle tue lezioni

Contro: aggiunte classi di eventi, sovraccarico forse di codice non necessario.

Ecco uno dei modi possibili di come potrebbe apparire:

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

Questo è solo uno dei modi possibili. Probabilmente non hai bisogno di così tanti eventi. E sono sicuro che puoi fare meglio a conoscere i tuoi dati, questo è solo uno dei tanti modi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.