Является ли нормальным C++ ООП дизайном хранить векторы с указателями, где один вектор хранит указатели на родителя, другие - на потомков?

Я сейчас учу C++ и пишу игру. У меня есть абстрактный класс GO (GameObject), который содержит много информации, справедливой для всех игровых объектов (хранит позицию, размер, содержит функцию отрисовки, содержит функцию реакции на новый ход, содержит функцию реакции на нажатие и т.д.). Игровых объектов, естественно, много, поэтому в классе MainScreen, содержащем основной цикл игры, я храню вектор с указателями на GO. При завершении MainScreen'а все GameObject'ы удаляются из памяти.

Проблема в том, что от класса GameObject наследуется очень много потомков со своей уникальной логикой, логику которых абстрагировать проблематично. Например, одним из потомков GameObject'а является абстрактный класс TerritoryB (TerritoryBuilding), содержащий метод bool allowBuilding(). Этот метод проверяет, можно ли построить здание в заданном месте (по логике игры здания можно строить только рядом с городскими центрами и дорогами, ведущим к городским центрам). Я думал о том, чтобы объявить allowBuilding() в GameObject'е как чисто виртуальный, в TerritoryB определить его своей логикой, а во всех остальных сделать так, чтобы метод всегда возвращал false, но тогда:

  • БОльшая нагрузка на процессор, потому что вместо проверки пары городских центров и пары десятков дорог надо будет вызывать методы сотни лесов, камней, гор и т.д.
  • Учитывая что TerritoryB далеко не один класс со своей уникальной логикой, по итогу GameObject будет содержать десятки, если не сотни, чисто виртуальных методов, которые, как мне кажется, там не должны быть (более критично)

Другой пример. Есть класс ResourcePoint, унаследованный от HPGO (GameObject с полоской здоровья). Каждый ResourcePoint содержит ресурсы. Количество ресурсов равно количеству здоровья. Ресурсы бывают многих типов, у ResourcePoint определен чисто виртуальный метод std::string getResourceType(), который должен возвращать строковой id ресурса. Потомки ResourcePoint (Store, Iron, Forest и т.д.) переопределяют этот метод в зависимости от того, какой ресурс содержит этот ResourcePoint. Получается, что можно либо опять перенести объявление getResourceType с ResourcePoint'а в GameObject, но тогда будут те же проблемы: избыточная нагрузка на процессор и захламление GameObject'а логикой, которой, как мне кажется, там быть не должно.

Поэтому я отказался от этой идеи и решил дополнительно хранить вектора с указателями на все объекты, содержащие уникальную логику. Таким образом у меня есть вектор с указателями на родителя (GameObject) и векторы с указателями на детей (TerritoryBuilding, ResourcePoint и т.д.). Вектор с указателями на GameObject'ы используется, когда мне нужно обработать логику, справедливую для всех GameObject'ов (например, отрисовка). Векторы с указателями на потомков используются, когда мне нужно обработать отдельную логику потомка. Когда мне нужно добавить объект, я добавляю его во все вектора, в котором он должен находится. При закрытии MainScreen'а из памяти удаляются оператором delete все GameObject'ы. Вот так это выглядит в коде:

объявление массивов

std::vector<GO*> *gameObjects;
std::vector<Unit*> *units;
std::vector<ResourcePoint*> *resourcePoints;
std::vector<TerritoryB*> *territoryBuildings;
std::vector<TerritoryOriginB*> *territoryOrigins;
std::vector<TerritoryConductorB*> *territoryConductors;

удаление

for (uint32_t i = 0; i < this->gameObjects->size(); i = i + 1) {
    delete this->gameObjects->at(i);
}
delete this->gameObjects;
delete this->units;
delete this->resourcePoints;
delete this->territoryBuildings;
delete this->territoryOrigins;
delete this->territoryConductors;

отрисовка

for (uint32_t i = 0; i < this->gameObjects->size(); i = i + 1) {
    window.draw(*this->gameObjects->at(i));
}

проверка при строительстве, свободна ли клетка

bool BuildingMode::empty() const {
    sf::IntRect rect1;
    rect1.left = this->b->getX();
    rect1.top = this->b->getY();
    rect1.width = this->b->getSX();
    rect1.height = this->b->getSY();

    for (uint32_t i = 0; i < go->size(); i = i + 1) {
        if (!go->at(i)->exist()) {
            continue;
        }
        sf::IntRect rect2;
        rect2.left = go->at(i)->getX();
        rect2.top = go->at(i)->getY();
        rect2.width = go->at(i)->getSX();
        rect2.height = go->at(i)->getSY();
        if (rect1.intersects(rect2)) {
            return false;
        }
    }

    return true;
}

проверка при строительстве, можно ли строить, учитывая правило дорог и городских центров

bool BuildingMode::controlled() const {
    uint32_t x = this->b->getX();
    uint32_t y = this->b->getY();
    uint32_t sx = this->b->getSX();
    uint32_t sy = this->b->getSY();

    for (uint32_t i = 0; i < tb->size(); i = i + 1) {
        if (tb->at(i)->allowBuilding(x, y, sx, sy, *this->player)) {
            return true;
        }
    }

    return false;
}

Нормальный ли это дизайн или лучше переписать, пока проект маленький и это возможно?


Ответы (0 шт):