Как я могу аккуратно и элегантно обрабатывать данные и зависимости между классами

12

Я работаю над 2D-игрой сверху вниз в SFML 2, и мне нужно найти элегантный способ, которым все будет работать и совмещаться.

Позвольте мне объяснить. У меня есть ряд классов, которые наследуются от абстрактной базы, которая предоставляет метод рисования и метод обновления для всех классов.

В игровом цикле я вызываю update, а затем рисую каждый класс, я думаю, что это довольно распространенный подход. У меня есть классы для плиток, столкновений, игрока и менеджера ресурсов, который содержит все плитки / изображения / текстуры. Из-за того, как ввод работает в SFML, я решил, что каждый класс обрабатывает ввод (если требуется) при вызове обновления.

До сих пор я передавал зависимости по мере необходимости, например, в классе игрока, когда нажимается клавиша перемещения, я вызываю метод класса столкновения, чтобы проверить, будет ли позиция, в которую игрок хочет переместиться, столкнуться, и перемещать игрока только если нет столкновения.

По большей части это работает нормально, но я верю, что это можно сделать лучше, я просто не знаю как.

Теперь у меня есть более сложные вещи, которые мне нужно реализовать, например: игрок может подойти к объекту на земле, нажать клавишу, чтобы поднять / разграбить его, и тогда он появится в инвентаре. Это означает, что должно произойти несколько вещей:

  • Проверьте, находится ли игрок в пределах досягаемости предмета при нажатии клавиши, иначе не продолжайте.
  • Найдите предмет.
  • Обновите текстуру спрайта на предмете с его текстуры по умолчанию до "разграбленной" текстуры.
  • Обновите столкновение для предмета: он мог изменить форму или был полностью удален.
  • Необходимо обновить инвентарь с добавленным элементом.

Как мне заставить все общаться? С моей нынешней системой я закончу с тем, что мои классы выйдут из области видимости и повсюду будут вызовы методов друг для друга. Я мог бы связать все классы в одном большом менеджере и дать каждому ссылку на родительский класс менеджера, но это кажется лишь немного лучше.

Любая помощь / совет будет принята с благодарностью! Если что-то неясно, я с удовольствием расскажу о вещах.

неофит
источник
1
Возможно, вы захотите рассмотреть композицию здесь, а не наследование. Посмотрите на примеры композиции, и это может дать вам некоторые идеи. Идиома прыщей тоже может помочь в решении проблем.
OriginalDaemon
5
Одна из канонических статей о композиции: « Развивай свою
иерархию»
Кажется слишком локализованным. Попробуйте Code Review SE?
Анко

Ответы:

5

Не уверен, что композиция решит все проблемы. Может быть, может частично помочь. Но если вам нужно разделить классы, я бы изучил логику, основанную на событиях. Таким образом, например, у вас будет функция OnLoot, которая должна иметь положение игрока и информацию о лото, чтобы найти ближайший. Затем функция отправляет событие украденному предмету. Загруженный элемент в цикле обработки события обрабатывает это событие, поэтому элементу нужно только знать, как его обновить. Функция OnLoot также может обновлять инвентарь игрока или сам элемент может отправлять событие updateInventory / * OnLootSucess *, и игрок / инвентарь будет обрабатывать его в своем собственном цикле событий процесса.

Плюсы: вы отделили некоторые из ваших классов

Минусы: добавлены классы событий, возможно, ненужные накладные расходы кода.

Вот один из возможных способов, как это может выглядеть:

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

Это только один из возможных способов. Вероятно, вам не нужно так много событий. И я уверен, что вы можете лучше знать свои данные, это только один из многих способов.

alariq
источник