¿Cómo puedo manejar de manera limpia y elegante los datos y las dependencias entre clases?

12

Estoy trabajando en el juego 2d topdown en SFML 2, y necesito encontrar una forma elegante en la que todo funcione y encaje.

Permíteme explicarte. Tengo una serie de clases que heredan de una base abstracta que proporciona un método de dibujo y un método de actualización para todas las clases.

En el ciclo del juego, llamo actualización y luego recurro a cada clase, imagino que este es un enfoque bastante común. Tengo clases de mosaicos, colisiones, el jugador y un administrador de recursos que contiene todos los mosaicos / imágenes / texturas. Debido a la forma en que funciona la entrada en SFML, decidí que cada clase maneje la entrada (si es necesario) en su llamada de actualización.

Hasta ahora he estado pasando dependencias según sea necesario, por ejemplo, en la clase de jugador cuando se presiona una tecla de movimiento, llamo a un método en la clase de colisión para verificar si la posición a la que el jugador quiere moverse será una colisión, y solo mueve al jugador si no hay colisión.

Esto funciona bien en su mayor parte, pero creo que se puede hacer mejor, pero no estoy seguro de cómo hacerlo.

Ahora tengo cosas más complejas que necesito implementar, por ejemplo: un jugador puede caminar hacia un objeto en el suelo, presionar una tecla para recogerlo / saquearlo y luego aparecerá en el inventario. Esto significa que deben suceder algunas cosas:

  • Verifique si el reproductor está dentro del alcance de un elemento lootable al presionar una tecla, de lo contrario no continúe.
  • Encuentra el artículo.
  • Actualice la textura de sprite en el elemento de su textura predeterminada a una textura "saqueada".
  • Actualice la colisión del artículo: podría haber cambiado de forma o haberse eliminado por completo.
  • El inventario debe actualizarse con el artículo agregado.

¿Cómo hago que todo se comunique? Con mi sistema actual terminaré con mis clases fuera de alcance, y las llamadas de método entre sí por todo el lugar. Podría vincular todas las clases en un gran administrador y dar a cada una una referencia a la clase de administrador principal, pero esto parece solo un poco mejor.

Cualquier ayuda / consejo sería muy apreciada! Si algo no está claro, estoy feliz de ampliar las cosas.

Neófito
fuente
1
Es posible que desee considerar la composición aquí, en lugar de la herencia. Eche un vistazo a los ejemplos de composición y podría darle algunas ideas. La expresión idiota también podría ayudar a resolver las cosas.
OriginalDaemon
55
Uno de los artículos canónicos sobre composición: Evoluciona tu jerarquía
doppelgreener
Parece demasiado localizado. Prueba el Code Review SE?
Anko

Respuestas:

5

No estoy seguro si la composición resolverá todos los problemas. Tal vez pueda ayudar parcialmente. Pero si lo que quieres es desacoplar clases, buscaría más lógica impulsada por eventos. De esta manera, por ejemplo, tendrá la función OnLoot que necesita tener la posición del jugador e información sobre los botines disponibles para encontrar el más cercano. Luego, la función envía un evento al artículo saqueado. El elemento saqueado en su ciclo de proceso de eventos maneja este evento, por lo que el elemento solo necesita saber cómo actualizarse. La función OnLoot también puede actualizar el inventario del jugador o el elemento mismo puede enviar el evento updateInventory / * OnLootSucess * y el jugador / inventario lo manejará en su propio ciclo de eventos de proceso.

Pros: has desacoplado algunas de tus clases

Contras: se agregaron clases de eventos, quizás una sobrecarga de código innecesaria.

Aquí hay una de las posibles formas de cómo se ve:

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 es solo una de las formas posibles. Probablemente no necesites tantos eventos. Y estoy seguro de que puede conocer mejor sus datos, esta es solo una de las muchas maneras.

alariq
fuente