Como posso lidar de forma limpa e elegante com dados e dependências entre classes

12

Estou trabalhando no jogo 2D de cima para baixo no SFML 2 e preciso encontrar uma maneira elegante de tudo funcionar e se encaixar.

Permita-me explicar. Eu tenho um número de classes que herdam de uma base abstrata que fornece um método de desenho e um método de atualização para todas as classes.

No ciclo do jogo, chamo de atualização e depois desenho em cada classe, imagino que essa seja uma abordagem bastante comum. Eu tenho aulas de blocos, colisões, o jogador e um gerenciador de recursos que contém todos os blocos / imagens / texturas. Devido à maneira como a entrada funciona na SFML, decidi que cada classe lidasse com a entrada (se necessário) em sua chamada de atualização.

Até agora, tenho passado as dependências conforme necessário, por exemplo, na classe de jogador quando uma tecla de movimento é pressionada, chamo um método na classe de colisão para verificar se a posição que o jogador deseja mover será uma colisão, e mova o jogador apenas se não houver colisão.

Na maioria das vezes, isso funciona bem, mas acredito que pode ser feito melhor, mas não sei como.

Agora tenho coisas mais complexas que preciso implementar, por exemplo: um jogador pode caminhar até um objeto no chão, pressionar uma tecla para buscá-lo / saquear e, em seguida, ele aparecerá no inventário. Isso significa que algumas coisas precisam acontecer:

  • Verifique se o jogador está ao alcance de um item para pilhagem ao pressionar a tecla, caso contrário, não prossiga.
  • Encontre o item.
  • Atualize a textura do sprite no item de sua textura padrão para uma textura "saqueada".
  • Atualize a colisão para o item: ele pode ter sido alterado ou removido completamente.
  • O inventário precisa ser atualizado com o item adicionado.

Como faço para que tudo se comunique? Com o meu sistema atual, terminarei com minhas aulas fora do escopo e o método chama um ao outro em todo o lugar. Eu poderia amarrar todas as classes em um grande gerente e dar a cada uma uma referência à classe de gerente pai, mas isso parece apenas um pouco melhor.

Qualquer ajuda / conselho seria muito apreciada! Se algo não estiver claro, fico feliz em expandir as coisas.

Neófito
fonte
1
Você pode considerar a composição aqui, em vez de herança. Dê uma olhada nos exemplos de composição e isso pode lhe dar algumas idéias. O idioma pimpl também pode ajudar a resolver as coisas.
OriginalDaemon
5
Uma das canon artigos sobre composição: Evolve sua hierarquia
doppelgreener
Parece muito localizado. Experimente o Code Review SE?
Anko

Respostas:

5

Não tenho certeza se a composição resolverá todos os problemas. Talvez possa ajudar parcialmente. Mas se o que você deseja é dissociar as classes, analisarei mais lógica orientada a eventos. Desta forma, por exemplo, você terá a função OnLoot , que precisa ter a posição do jogador e informações sobre os saques disponíveis para encontrar o mais próximo. Então a função envia evento para o item saqueado. O item saqueado em seu ciclo de processo de eventos lida com esse evento, portanto, o item precisa saber apenas como se atualizar. A função OnLoot também pode atualizar o inventário do player ou o próprio item pode enviar o evento updateInventory / * OnLootSucess * e o player / inventário o manipulará em seu próprio ciclo de eventos do processo.

Prós: você separou algumas de suas aulas

Contras: adicionadas classes de eventos, talvez sobrecarga desnecessária de código.

Aqui está uma das maneiras possíveis de como ela pode parecer:

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

Esta é apenas uma das maneiras possíveis. Provavelmente você não precisa de tantos eventos. E tenho certeza de que você pode conhecer melhor seus dados. Essa é apenas uma das muitas maneiras.

alariq
fonte